diff --git a/Makefile b/Makefile index baceefe6d49ff..9e69eb7922925 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ lint-diff: git diff upstream/master --name-only -- "*.py" | xargs flake8 black: - black . --exclude '(asv_bench/env|\.egg|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|_build|buck-out|build|dist)' + black . --exclude '(asv_bench/env|\.egg|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|_build|buck-out|build|dist|setup.py)' develop: build python setup.py develop diff --git a/ci/code_checks.sh b/ci/code_checks.sh index 96a8440d85694..06d45e38bfcdb 100755 --- a/ci/code_checks.sh +++ b/ci/code_checks.sh @@ -56,7 +56,7 @@ if [[ -z "$CHECK" || "$CHECK" == "lint" ]]; then black --version MSG='Checking black formatting' ; echo $MSG - black . --check --exclude '(asv_bench/env|\.egg|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|_build|buck-out|build|dist)' + black . --check --exclude '(asv_bench/env|\.egg|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|_build|buck-out|build|dist|setup.py)' RET=$(($RET + $?)) ; echo $MSG "DONE" # `setup.cfg` contains the list of error codes that are being ignored in flake8 diff --git a/doc/source/development/extending.rst b/doc/source/development/extending.rst index b492a4edd70a4..e341dcb8318bc 100644 --- a/doc/source/development/extending.rst +++ b/doc/source/development/extending.rst @@ -441,5 +441,22 @@ This would be more or less equivalent to: The backend module can then use other visualization tools (Bokeh, Altair,...) to generate the plots. +Libraries implementing the plotting backend should use `entry points `__ +to make their backend discoverable to pandas. The key is ``"pandas_plotting_backends"``. For example, pandas +registers the default "matplotlib" backend as follows. + +.. code-block:: python + + # in setup.py + setup( # noqa: F821 + ..., + entry_points={ + "pandas_plotting_backends": [ + "matplotlib = pandas:plotting._matplotlib", + ], + }, + ) + + More information on how to implement a third-party plotting backend can be found at https://github.com/pandas-dev/pandas/blob/master/pandas/plotting/__init__.py#L1. diff --git a/doc/source/install.rst b/doc/source/install.rst index 352b56ebd3020..fc99b458fa0af 100644 --- a/doc/source/install.rst +++ b/doc/source/install.rst @@ -15,35 +15,10 @@ Instructions for installing from source, `PyPI `__, `ActivePython `__, various Linux distributions, or a `development version `__ are also provided. -.. _install.dropping-27: - -Plan for dropping Python 2.7 ----------------------------- - -The Python core team plans to stop supporting Python 2.7 on January 1st, 2020. -In line with `NumPy's plans`_, all pandas releases through December 31, 2018 -will support Python 2. - -The 0.24.x feature release will be the last release to -support Python 2. The released package will continue to be available on -PyPI and through conda. - - Starting **January 1, 2019**, all new feature releases (> 0.24) will be Python 3 only. - -If there are people interested in continued support for Python 2.7 past December -31, 2018 (either backporting bug fixes or funding) please reach out to the -maintainers on the issue tracker. - -For more information, see the `Python 3 statement`_ and the `Porting to Python 3 guide`_. - -.. _NumPy's plans: https://github.com/numpy/numpy/blob/master/doc/neps/nep-0014-dropping-python2.7-proposal.rst#plan-for-dropping-python-27-support -.. _Python 3 statement: http://python3statement.org/ -.. _Porting to Python 3 guide: https://docs.python.org/3/howto/pyporting.html - Python version support ---------------------- -Officially Python 2.7, 3.5, 3.6, and 3.7. +Officially Python 3.5.3 and above, 3.6, and 3.7. Installing pandas ----------------- diff --git a/doc/source/whatsnew/v0.23.0.rst b/doc/source/whatsnew/v0.23.0.rst index 62cf977d8c8ac..f4c283ea742f7 100644 --- a/doc/source/whatsnew/v0.23.0.rst +++ b/doc/source/whatsnew/v0.23.0.rst @@ -31,7 +31,7 @@ Check the :ref:`API Changes ` and :ref:`deprecations .. warning:: Starting January 1, 2019, pandas feature releases will support Python 3 only. - See :ref:`install.dropping-27` for more. + See `Dropping Python 2.7 `_ for more. .. contents:: What's new in v0.23.0 :local: diff --git a/doc/source/whatsnew/v0.23.1.rst b/doc/source/whatsnew/v0.23.1.rst index d730a57a01a60..03b7d9db6bc63 100644 --- a/doc/source/whatsnew/v0.23.1.rst +++ b/doc/source/whatsnew/v0.23.1.rst @@ -12,7 +12,7 @@ and bug fixes. We recommend that all users upgrade to this version. .. warning:: Starting January 1, 2019, pandas feature releases will support Python 3 only. - See :ref:`install.dropping-27` for more. + See `Dropping Python 2.7 `_ for more. .. contents:: What's new in v0.23.1 :local: diff --git a/doc/source/whatsnew/v0.23.2.rst b/doc/source/whatsnew/v0.23.2.rst index df8cc12e3385e..9f24092d1d4ae 100644 --- a/doc/source/whatsnew/v0.23.2.rst +++ b/doc/source/whatsnew/v0.23.2.rst @@ -17,7 +17,7 @@ and bug fixes. We recommend that all users upgrade to this version. .. warning:: Starting January 1, 2019, pandas feature releases will support Python 3 only. - See :ref:`install.dropping-27` for more. + See `Dropping Python 2.7 `_ for more. .. contents:: What's new in v0.23.2 :local: diff --git a/doc/source/whatsnew/v0.23.4.rst b/doc/source/whatsnew/v0.23.4.rst index 060d1fc8eba34..eadac6f569926 100644 --- a/doc/source/whatsnew/v0.23.4.rst +++ b/doc/source/whatsnew/v0.23.4.rst @@ -12,7 +12,7 @@ and bug fixes. We recommend that all users upgrade to this version. .. warning:: Starting January 1, 2019, pandas feature releases will support Python 3 only. - See :ref:`install.dropping-27` for more. + See `Dropping Python 2.7 `_ for more. .. contents:: What's new in v0.23.4 :local: diff --git a/doc/source/whatsnew/v0.24.0.rst b/doc/source/whatsnew/v0.24.0.rst index a66056f661de3..d9f41d2a75116 100644 --- a/doc/source/whatsnew/v0.24.0.rst +++ b/doc/source/whatsnew/v0.24.0.rst @@ -6,7 +6,7 @@ What's new in 0.24.0 (January 25, 2019) .. warning:: The 0.24.x series of releases will be the last to support Python 2. Future feature - releases will support Python 3 only. See :ref:`install.dropping-27` for more + releases will support Python 3 only. See `Dropping Python 2.7 `_ for more details. {{ header }} diff --git a/doc/source/whatsnew/v0.24.1.rst b/doc/source/whatsnew/v0.24.1.rst index 1b0232cad7476..aead8c48eb9b7 100644 --- a/doc/source/whatsnew/v0.24.1.rst +++ b/doc/source/whatsnew/v0.24.1.rst @@ -6,7 +6,7 @@ Whats new in 0.24.1 (February 3, 2019) .. warning:: The 0.24.x series of releases will be the last to support Python 2. Future feature - releases will support Python 3 only. See :ref:`install.dropping-27` for more. + releases will support Python 3 only. See `Dropping Python 2.7 `_ for more. {{ header }} diff --git a/doc/source/whatsnew/v0.24.2.rst b/doc/source/whatsnew/v0.24.2.rst index da8064893e8a8..d1a893f99cff4 100644 --- a/doc/source/whatsnew/v0.24.2.rst +++ b/doc/source/whatsnew/v0.24.2.rst @@ -6,7 +6,7 @@ Whats new in 0.24.2 (March 12, 2019) .. warning:: The 0.24.x series of releases will be the last to support Python 2. Future feature - releases will support Python 3 only. See :ref:`install.dropping-27` for more. + releases will support Python 3 only. See `Dropping Python 2.7 `_ for more. {{ header }} diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 42e756635e739..5b8f980d27b9d 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -6,7 +6,7 @@ What's new in 0.25.0 (July 18, 2019) .. warning:: Starting with the 0.25.x series of releases, pandas only supports Python 3.5.3 and higher. - See :ref:`install.dropping-27` for more details. + See `Dropping Python 2.7 `_ for more details. .. warning:: diff --git a/doc/source/whatsnew/v0.25.1.rst b/doc/source/whatsnew/v0.25.1.rst index 6234bc0f7bd35..fb67decb46b64 100644 --- a/doc/source/whatsnew/v0.25.1.rst +++ b/doc/source/whatsnew/v0.25.1.rst @@ -50,20 +50,21 @@ Timedelta Timezones ^^^^^^^^^ -- +- Bug in :class:`Index` where a numpy object array with a timezone aware :class:`Timestamp` and ``np.nan`` would not return a :class:`DatetimeIndex` (:issue:`27011`) - - Numeric ^^^^^^^ -- +- Bug in :meth:`Series.interpolate` when using a timezone aware :class:`DatetimeIndex` (:issue:`27548`) +- Bug when printing negative floating point complex numbers would raise an ``IndexError`` (:issue:`27484`) - - Conversion ^^^^^^^^^^ -- +- Improved the warnings for the deprecated methods :meth:`Series.real` and :meth:`Series.imag` (:issue:`27610`) - - @@ -113,21 +114,21 @@ I/O Plotting ^^^^^^^^ -- +- Added a pandas_plotting_backends entrypoint group for registering plot backends. See :ref:`extending.plotting-backends` for more (:issue:`26747`). - - Groupby/resample/rolling ^^^^^^^^^^^^^^^^^^^^^^^^ -- +- Bug in :meth:`pandas.core.groupby.DataFrameGroupBy.transform` where applying a timezone conversion lambda function would drop timezone information (:issue:`27496`) - - Reshaping ^^^^^^^^^ -- +- A ``KeyError`` is now raised if ``.unstack()`` is called on a :class:`Series` or :class:`DataFrame` with a flat :class:`Index` passing a name which is not the correct one (:issue:`18303`) - - diff --git a/pandas/core/frame.py b/pandas/core/frame.py index c15f4ad8e1900..245e41ed16eb2 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -2601,12 +2601,12 @@ def memory_usage(self, index=True, deep=False): ... for t in dtypes]) >>> df = pd.DataFrame(data) >>> df.head() - int64 float64 complex128 object bool - 0 1 1.0 1.0+0.0j 1 True - 1 1 1.0 1.0+0.0j 1 True - 2 1 1.0 1.0+0.0j 1 True - 3 1 1.0 1.0+0.0j 1 True - 4 1 1.0 1.0+0.0j 1 True + int64 float64 complex128 object bool + 0 1 1.0 1.000000+0.000000j 1 True + 1 1 1.0 1.000000+0.000000j 1 True + 2 1 1.0 1.000000+0.000000j 1 True + 3 1 1.0 1.000000+0.000000j 1 True + 4 1 1.0 1.000000+0.000000j 1 True >>> df.memory_usage() Index 128 diff --git a/pandas/core/generic.py b/pandas/core/generic.py index f28f58b070368..19f126c36cde7 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -30,7 +30,6 @@ is_bool, is_bool_dtype, is_datetime64_any_dtype, - is_datetime64_dtype, is_datetime64tz_dtype, is_dict_like, is_extension_array_dtype, @@ -7035,7 +7034,7 @@ def interpolate( methods = {"index", "values", "nearest", "time"} is_numeric_or_datetime = ( is_numeric_dtype(index) - or is_datetime64_dtype(index) + or is_datetime64_any_dtype(index) or is_timedelta64_dtype(index) ) if method not in methods and not is_numeric_or_datetime: diff --git a/pandas/core/groupby/generic.py b/pandas/core/groupby/generic.py index 7fd0ca94e7997..5b9cec6903749 100644 --- a/pandas/core/groupby/generic.py +++ b/pandas/core/groupby/generic.py @@ -42,7 +42,7 @@ from pandas.core.base import DataError, SpecificationError import pandas.core.common as com from pandas.core.frame import DataFrame -from pandas.core.generic import NDFrame, _shared_docs +from pandas.core.generic import ABCDataFrame, ABCSeries, NDFrame, _shared_docs from pandas.core.groupby import base from pandas.core.groupby.groupby import GroupBy, _apply_docs, _transform_template from pandas.core.index import Index, MultiIndex @@ -1025,8 +1025,8 @@ def transform(self, func, *args, **kwargs): object.__setattr__(group, "name", name) res = wrapper(group) - if hasattr(res, "values"): - res = res.values + if isinstance(res, (ABCDataFrame, ABCSeries)): + res = res._values indexer = self._get_index(name) s = klass(res, indexer) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 33de8e41b2f65..12923fd790972 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -489,19 +489,15 @@ def __new__( pass elif inferred != "string": if inferred.startswith("datetime"): - if ( - lib.is_datetime_with_singletz_array(subarr) - or "tz" in kwargs - ): - # only when subarr has the same tz - from pandas import DatetimeIndex + from pandas import DatetimeIndex - try: - return DatetimeIndex( - subarr, copy=copy, name=name, **kwargs - ) - except OutOfBoundsDatetime: - pass + try: + return DatetimeIndex(subarr, copy=copy, name=name, **kwargs) + except (ValueError, OutOfBoundsDatetime): + # GH 27011 + # If we have mixed timezones, just send it + # down the base constructor + pass elif inferred.startswith("timedelta"): from pandas import TimedeltaIndex @@ -1550,7 +1546,11 @@ def _validate_index_level(self, level): "Too many levels:" " Index has only 1 level, not %d" % (level + 1) ) elif level != self.name: - raise KeyError("Level %s must be same as name (%s)" % (level, self.name)) + raise KeyError( + "Requested level ({}) does not match index name ({})".format( + level, self.name + ) + ) def _get_level_number(self, level): self._validate_index_level(level) diff --git a/pandas/core/reshape/reshape.py b/pandas/core/reshape/reshape.py index 540a06caec220..a24900543b81a 100644 --- a/pandas/core/reshape/reshape.py +++ b/pandas/core/reshape/reshape.py @@ -12,6 +12,7 @@ ensure_platform_int, is_bool_dtype, is_extension_array_dtype, + is_integer, is_integer_dtype, is_list_like, is_object_dtype, @@ -402,6 +403,10 @@ def unstack(obj, level, fill_value=None): else: level = level[0] + # Prioritize integer interpretation (GH #21677): + if not is_integer(level) and not level == "__placeholder__": + level = obj.index._get_level_number(level) + if isinstance(obj, DataFrame): if isinstance(obj.index, MultiIndex): return _unstack_frame(obj, level, fill_value=fill_value) diff --git a/pandas/core/series.py b/pandas/core/series.py index 59ea8c6bd6c5d..42afb3537c5d8 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -958,7 +958,9 @@ def real(self): .. deprecated 0.25.0 """ warnings.warn( - "`real` has be deprecated and will be removed in a " "future verison", + "`real` is deprecated and will be removed in a future version. " + "To eliminate this warning for a Series `ser`, use " + "`np.real(ser.to_numpy())` or `ser.to_numpy().real`.", FutureWarning, stacklevel=2, ) @@ -976,7 +978,9 @@ def imag(self): .. deprecated 0.25.0 """ warnings.warn( - "`imag` has be deprecated and will be removed in a " "future verison", + "`imag` is deprecated and will be removed in a future version. " + "To eliminate this warning for a Series `ser`, use " + "`np.imag(ser.to_numpy())` or `ser.to_numpy().imag`.", FutureWarning, stacklevel=2, ) diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py index 0e8ed7b25d665..11275973e2bb8 100644 --- a/pandas/io/formats/format.py +++ b/pandas/io/formats/format.py @@ -5,6 +5,7 @@ from functools import partial from io import StringIO +import re from shutil import get_terminal_size from unicodedata import east_asian_width @@ -1584,17 +1585,10 @@ def _trim_zeros_complex(str_complexes, na_rep="NaN"): Separates the real and imaginary parts from the complex number, and executes the _trim_zeros_float method on each of those. """ - - def separate_and_trim(str_complex, na_rep): - num_arr = str_complex.split("+") - return ( - _trim_zeros_float([num_arr[0]], na_rep) - + ["+"] - + _trim_zeros_float([num_arr[1][:-1]], na_rep) - + ["j"] - ) - - return ["".join(separate_and_trim(x, na_rep)) for x in str_complexes] + return [ + "".join(_trim_zeros_float(re.split(r"([j+-])", x), na_rep)) + for x in str_complexes + ] def _trim_zeros_float(str_floats, na_rep="NaN"): diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 0610780edb28d..a3c1499845c2a 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -1533,6 +1533,53 @@ def hexbin(self, x, y, C=None, reduce_C_function=None, gridsize=None, **kwargs): return self(kind="hexbin", x=x, y=y, C=C, **kwargs) +_backends = {} + + +def _find_backend(backend: str): + """ + Find a pandas plotting backend> + + Parameters + ---------- + backend : str + The identifier for the backend. Either an entrypoint item registered + with pkg_resources, or a module name. + + Notes + ----- + Modifies _backends with imported backends as a side effect. + + Returns + ------- + types.ModuleType + The imported backend. + """ + import pkg_resources # Delay import for performance. + + for entry_point in pkg_resources.iter_entry_points("pandas_plotting_backends"): + if entry_point.name == "matplotlib": + # matplotlib is an optional dependency. When + # missing, this would raise. + continue + _backends[entry_point.name] = entry_point.load() + + try: + return _backends[backend] + except KeyError: + # Fall back to unregisted, module name approach. + try: + module = importlib.import_module(backend) + except ImportError: + # We re-raise later on. + pass + else: + _backends[backend] = module + return module + + raise ValueError("No backend {}".format(backend)) + + def _get_plot_backend(backend=None): """ Return the plotting backend to use (e.g. `pandas.plotting._matplotlib`). @@ -1546,7 +1593,18 @@ def _get_plot_backend(backend=None): The backend is imported lazily, as matplotlib is a soft dependency, and pandas can be used without it being installed. """ - backend_str = backend or pandas.get_option("plotting.backend") - if backend_str == "matplotlib": - backend_str = "pandas.plotting._matplotlib" - return importlib.import_module(backend_str) + backend = backend or pandas.get_option("plotting.backend") + + if backend == "matplotlib": + # Because matplotlib is an optional dependency and first-party backend, + # we need to attempt an import here to raise an ImportError if needed. + import pandas.plotting._matplotlib as module + + _backends["matplotlib"] = module + + if backend in _backends: + return _backends[backend] + + module = _find_backend(backend) + _backends[backend] = module + return module diff --git a/pandas/tests/arrays/interval/test_interval.py b/pandas/tests/arrays/interval/test_interval.py index 82409df5b46f7..6a86289b6fcc6 100644 --- a/pandas/tests/arrays/interval/test_interval.py +++ b/pandas/tests/arrays/interval/test_interval.py @@ -42,10 +42,9 @@ class TestAttributes: (0, 1), (Timedelta("0 days"), Timedelta("1 day")), (Timestamp("2018-01-01"), Timestamp("2018-01-02")), - pytest.param( + ( Timestamp("2018-01-01", tz="US/Eastern"), Timestamp("2018-01-02", tz="US/Eastern"), - marks=pytest.mark.xfail(strict=True, reason="GH 27011"), ), ], ) diff --git a/pandas/tests/frame/test_alter_axes.py b/pandas/tests/frame/test_alter_axes.py index c57b2a6964f39..a6fd980faefcd 100644 --- a/pandas/tests/frame/test_alter_axes.py +++ b/pandas/tests/frame/test_alter_axes.py @@ -1083,7 +1083,7 @@ def test_reset_index_level(self): # Missing levels - for both MultiIndex and single-level Index: for idx_lev in ["A", "B"], ["A"]: - with pytest.raises(KeyError, match="Level E "): + with pytest.raises(KeyError, match=r"(L|l)evel \(?E\)?"): df.set_index(idx_lev).reset_index(level=["A", "E"]) with pytest.raises(IndexError, match="Too many levels"): df.set_index(idx_lev).reset_index(level=[0, 1, 2]) diff --git a/pandas/tests/groupby/test_transform.py b/pandas/tests/groupby/test_transform.py index 1eab3ba253f4d..9a8b7cf18f2c0 100644 --- a/pandas/tests/groupby/test_transform.py +++ b/pandas/tests/groupby/test_transform.py @@ -1001,3 +1001,27 @@ def test_ffill_not_in_axis(func, key, val): expected = df assert_frame_equal(result, expected) + + +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", + ) + assert_series_equal(result, expected) diff --git a/pandas/tests/indexes/datetimes/test_construction.py b/pandas/tests/indexes/datetimes/test_construction.py index 6708feda7dd1e..66a22ae7e9e46 100644 --- a/pandas/tests/indexes/datetimes/test_construction.py +++ b/pandas/tests/indexes/datetimes/test_construction.py @@ -822,6 +822,12 @@ def test_constructor_wrong_precision_raises(self): with pytest.raises(ValueError): pd.DatetimeIndex(["2000"], dtype="datetime64[us]") + 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) + class TestTimeSeries: def test_dti_constructor_preserve_dti_freq(self): diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index e75d80bec1fdf..c40a9bce9385b 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -2004,7 +2004,7 @@ def test_isin_level_kwarg_bad_label_raises(self, label, indices): msg = "'Level {} not found'" else: index = index.rename("foo") - msg = r"'Level {} must be same as name \(foo\)'" + msg = r"Requested level \({}\) does not match index name \(foo\)" with pytest.raises(KeyError, match=msg.format(label)): index.isin([], level=label) diff --git a/pandas/tests/indexes/test_common.py b/pandas/tests/indexes/test_common.py index 0e9aa07a4c05a..ae1a21e9b3980 100644 --- a/pandas/tests/indexes/test_common.py +++ b/pandas/tests/indexes/test_common.py @@ -35,7 +35,8 @@ def test_droplevel(self, indices): for level in "wrong", ["wrong"]: with pytest.raises( - KeyError, match=re.escape("'Level wrong must be same as name (None)'") + KeyError, + match=r"'Requested level \(wrong\) does not match index name \(None\)'", ): indices.droplevel(level) @@ -200,7 +201,7 @@ def test_unique(self, indices): with pytest.raises(IndexError, match=msg): indices.unique(level=3) - msg = r"Level wrong must be same as name \({}\)".format( + msg = r"Requested level \(wrong\) does not match index name \({}\)".format( re.escape(indices.name.__repr__()) ) with pytest.raises(KeyError, match=msg): diff --git a/pandas/tests/io/formats/test_format.py b/pandas/tests/io/formats/test_format.py index 818bbc566aca8..ad47f714c9550 100644 --- a/pandas/tests/io/formats/test_format.py +++ b/pandas/tests/io/formats/test_format.py @@ -1537,7 +1537,7 @@ def test_to_string_float_index(self): assert result == expected def test_to_string_complex_float_formatting(self): - # GH #25514 + # GH #25514, 25745 with pd.option_context("display.precision", 5): df = DataFrame( { @@ -1545,6 +1545,7 @@ def test_to_string_complex_float_formatting(self): (0.4467846931321966 + 0.0715185102060818j), (0.2739442392974528 + 0.23515228785438969j), (0.26974928742135185 + 0.3250604054898979j), + (-1j), ] } ) @@ -1552,7 +1553,8 @@ def test_to_string_complex_float_formatting(self): expected = ( " x\n0 0.44678+0.07152j\n" "1 0.27394+0.23515j\n" - "2 0.26975+0.32506j" + "2 0.26975+0.32506j\n" + "3 -0.00000-1.00000j" ) assert result == expected diff --git a/pandas/tests/plotting/test_backend.py b/pandas/tests/plotting/test_backend.py index 51f2abb6cc2f4..e79e7b6239eb3 100644 --- a/pandas/tests/plotting/test_backend.py +++ b/pandas/tests/plotting/test_backend.py @@ -1,5 +1,11 @@ +import sys +import types + +import pkg_resources import pytest +import pandas.util._test_decorators as td + import pandas @@ -36,3 +42,44 @@ def test_backend_is_correct(monkeypatch): pandas.set_option("plotting.backend", "matplotlib") except ImportError: pass + + +@td.skip_if_no_mpl +def test_register_entrypoint(): + mod = types.ModuleType("my_backend") + mod.plot = lambda *args, **kwargs: 1 + + backends = pkg_resources.get_entry_map("pandas") + my_entrypoint = pkg_resources.EntryPoint( + "pandas_plotting_backend", + mod.__name__, + dist=pkg_resources.get_distribution("pandas"), + ) + backends["pandas_plotting_backends"]["my_backend"] = my_entrypoint + # TODO: the docs recommend importlib.util.module_from_spec. But this works for now. + sys.modules["my_backend"] = mod + + result = pandas.plotting._core._get_plot_backend("my_backend") + assert result is mod + + # TODO: https://github.com/pandas-dev/pandas/issues/27517 + # Remove the td.skip_if_no_mpl + with pandas.option_context("plotting.backend", "my_backend"): + result = pandas.plotting._core._get_plot_backend() + + assert result is mod + + +def test_register_import(): + mod = types.ModuleType("my_backend2") + mod.plot = lambda *args, **kwargs: 1 + sys.modules["my_backend2"] = mod + + result = pandas.plotting._core._get_plot_backend("my_backend2") + assert result is mod + + +@td.skip_if_mpl +def test_no_matplotlib_ok(): + with pytest.raises(ImportError): + pandas.plotting._core._get_plot_backend("matplotlib") diff --git a/pandas/tests/series/test_alter_axes.py b/pandas/tests/series/test_alter_axes.py index 63baa6af7c02a..11add8d61deeb 100644 --- a/pandas/tests/series/test_alter_axes.py +++ b/pandas/tests/series/test_alter_axes.py @@ -322,9 +322,9 @@ def test_reset_index_drop_errors(self): # KeyError raised for series index when passed level name is missing s = Series(range(4)) - with pytest.raises(KeyError, match="must be same as name"): + with pytest.raises(KeyError, match="does not match index name"): s.reset_index("wrong", drop=True) - with pytest.raises(KeyError, match="must be same as name"): + 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 diff --git a/pandas/tests/series/test_missing.py b/pandas/tests/series/test_missing.py index c5fc52b9b0c41..10375719be8d2 100644 --- a/pandas/tests/series/test_missing.py +++ b/pandas/tests/series/test_missing.py @@ -1518,10 +1518,16 @@ def test_interp_nonmono_raise(self): s.interpolate(method="krogh") @td.skip_if_no_scipy - def test_interp_datetime64(self): - df = Series([1, np.nan, 3], index=date_range("1/1/2000", periods=3)) - result = df.interpolate(method="nearest") - expected = Series([1.0, 1.0, 3.0], index=date_range("1/1/2000", periods=3)) + @pytest.mark.parametrize("method", ["nearest", "pad"]) + def test_interp_datetime64(self, method, tz_naive_fixture): + df = Series( + [1, np.nan, 3], index=date_range("1/1/2000", periods=3, tz=tz_naive_fixture) + ) + 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), + ) assert_series_equal(result, expected) def test_interp_limit_no_nans(self): diff --git a/pandas/tests/test_multilevel.py b/pandas/tests/test_multilevel.py index c97c69c323b56..dc4db6e7902a8 100644 --- a/pandas/tests/test_multilevel.py +++ b/pandas/tests/test_multilevel.py @@ -524,6 +524,22 @@ def test_stack_unstack_preserve_names(self): restacked = unstacked.stack() assert restacked.index.names == self.frame.index.names + @pytest.mark.parametrize("method", ["stack", "unstack"]) + def test_stack_unstack_wrong_level_name(self, method): + # GH 18303 - wrong level name should raise + + # A DataFrame with flat axes: + df = self.frame.loc["foo"] + + with pytest.raises(KeyError, match="does not match index name"): + getattr(df, method)("mistake") + + if method == "unstack": + # Same on a Series: + s = df.iloc[:, 0] + with pytest.raises(KeyError, match="does not match index name"): + getattr(s, method)("mistake") + def test_unstack_level_name(self): result = self.frame.unstack("second") expected = self.frame.unstack(level=1) diff --git a/setup.py b/setup.py index 53e12da53cdeb..d2c6b18b892cd 100755 --- a/setup.py +++ b/setup.py @@ -830,5 +830,10 @@ def srcpath(name=None, suffix=".pyx", subdir="src"): "hypothesis>=3.58", ] }, + entry_points={ + "pandas_plotting_backends": [ + "matplotlib = pandas:plotting._matplotlib", + ], + }, **setuptools_kwargs )