From f87fe147c7494f3db56f3de31aeda12f80ef9c67 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 19 Sep 2018 13:31:10 -0500 Subject: [PATCH 01/85] CI: Publish test summary (#22770) --- ci/azure/macos.yml | 4 ++++ ci/azure/windows-py27.yml | 6 +++++- ci/azure/windows.yml | 6 +++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ci/azure/macos.yml b/ci/azure/macos.yml index 25b66615dac7e5..5bf8d18d6cbb9b 100644 --- a/ci/azure/macos.yml +++ b/ci/azure/macos.yml @@ -37,3 +37,7 @@ jobs: - script: | export PATH=$HOME/miniconda3/bin:$PATH source activate pandas && pushd /tmp && python -c "import pandas; pandas.show_versions();" && popd + - task: PublishTestResults@2 + inputs: + testResultsFiles: '/tmp/*.xml' + testRunTitle: 'MacOS-35' diff --git a/ci/azure/windows-py27.yml b/ci/azure/windows-py27.yml index e60844896b71cf..3e92c962639300 100644 --- a/ci/azure/windows-py27.yml +++ b/ci/azure/windows-py27.yml @@ -37,5 +37,9 @@ jobs: displayName: 'Build' - script: | call activate %CONDA_ENV% - pytest --skip-slow --skip-network pandas -n 2 -r sxX --strict %* + pytest --junitxml=test-data.xml --skip-slow --skip-network pandas -n 2 -r sxX --strict %* displayName: 'Test' + - task: PublishTestResults@2 + inputs: + testResultsFiles: 'test-data.xml' + testRunTitle: 'Windows 27' diff --git a/ci/azure/windows.yml b/ci/azure/windows.yml index 6090139fb4f3e3..2ab8c6f3201886 100644 --- a/ci/azure/windows.yml +++ b/ci/azure/windows.yml @@ -28,5 +28,9 @@ jobs: displayName: 'Build' - script: | call activate %CONDA_ENV% - pytest --skip-slow --skip-network pandas -n 2 -r sxX --strict %* + pytest --junitxml=test-data.xml --skip-slow --skip-network pandas -n 2 -r sxX --strict %* displayName: 'Test' + - task: PublishTestResults@2 + inputs: + testResultsFiles: 'test-data.xml' + testRunTitle: 'Windows 36' From 1c113db60b68c5a262d64e92dc9de72bfe59aed5 Mon Sep 17 00:00:00 2001 From: Yeojin Kim <38222260+yeojin-dev@users.noreply.github.com> Date: Thu, 20 Sep 2018 06:17:12 +0900 Subject: [PATCH 02/85] BUG: Check types in Index.__contains__ (#22085) (#22602) --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/core/indexes/numeric.py | 23 +++++++++++++++++++++-- pandas/tests/indexing/test_indexing.py | 15 +++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 487d5d0d2accda..9e2c20c78f4899 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -723,6 +723,7 @@ Indexing - ``Float64Index.get_loc`` now raises ``KeyError`` when boolean key passed. (:issue:`19087`) - Bug in :meth:`DataFrame.loc` when indexing with an :class:`IntervalIndex` (:issue:`19977`) - :class:`Index` no longer mangles ``None``, ``NaN`` and ``NaT``, i.e. they are treated as three different keys. However, for numeric Index all three are still coerced to a ``NaN`` (:issue:`22332`) +- Bug in `scalar in Index` if scalar is a float while the ``Index`` is of integer dtype (:issue:`22085`) Missing ^^^^^^^ diff --git a/pandas/core/indexes/numeric.py b/pandas/core/indexes/numeric.py index 8d616468a87d95..7f64fb744c682c 100644 --- a/pandas/core/indexes/numeric.py +++ b/pandas/core/indexes/numeric.py @@ -6,6 +6,7 @@ pandas_dtype, needs_i8_conversion, is_integer_dtype, + is_float, is_bool, is_bool_dtype, is_scalar) @@ -162,7 +163,25 @@ def insert(self, loc, item): ) -class Int64Index(NumericIndex): +class IntegerIndex(NumericIndex): + """ + This is an abstract class for Int64Index, UInt64Index. + """ + + def __contains__(self, key): + """ + Check if key is a float and has a decimal. If it has, return False. + """ + hash(key) + try: + if is_float(key) and int(key) != key: + return False + return key in self._engine + except (OverflowError, TypeError, ValueError): + return False + + +class Int64Index(IntegerIndex): __doc__ = _num_index_shared_docs['class_descr'] % _int64_descr_args _typ = 'int64index' @@ -220,7 +239,7 @@ def _assert_safe_casting(cls, data, subarr): ) -class UInt64Index(NumericIndex): +class UInt64Index(IntegerIndex): __doc__ = _num_index_shared_docs['class_descr'] % _uint64_descr_args _typ = 'uint64index' diff --git a/pandas/tests/indexing/test_indexing.py b/pandas/tests/indexing/test_indexing.py index 33b7c1b8154c7c..761c633f89da33 100644 --- a/pandas/tests/indexing/test_indexing.py +++ b/pandas/tests/indexing/test_indexing.py @@ -631,6 +631,21 @@ def test_mixed_index_not_contains(self, index, val): # GH 19860 assert val not in index + def test_contains_with_float_index(self): + # GH#22085 + integer_index = pd.Int64Index([0, 1, 2, 3]) + uinteger_index = pd.UInt64Index([0, 1, 2, 3]) + float_index = pd.Float64Index([0.1, 1.1, 2.2, 3.3]) + + for index in (integer_index, uinteger_index): + assert 1.1 not in index + assert 1.0 in index + assert 1 in index + + assert 1.1 in float_index + assert 1.0 not in float_index + assert 1 not in float_index + def test_index_type_coercion(self): with catch_warnings(record=True): From 117d0b1011c090b4658b0e84c2b572ee713e21de Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Thu, 20 Sep 2018 09:40:10 -0400 Subject: [PATCH 03/85] BUG: Empty CategoricalIndex fails with boolean categories (#22710) * TST: Add failing test for empty bool Categoricals * BUG: Failure in empty boolean CategoricalIndex Fixes GH #22702. --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/core/arrays/categorical.py | 8 ++++++-- pandas/tests/arrays/categorical/test_constructors.py | 6 ++++++ pandas/tests/indexes/test_category.py | 6 ++++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 9e2c20c78f4899..e2ba35c1ad7f9f 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -616,6 +616,7 @@ Categorical ^^^^^^^^^^^ - Bug in :meth:`Categorical.from_codes` where ``NaN`` values in ``codes`` were silently converted to ``0`` (:issue:`21767`). In the future this will raise a ``ValueError``. Also changes the behavior of ``.from_codes([1.1, 2.0])``. +- Constructing a :class:`pd.CategoricalIndex` with empty values and boolean categories was raising a ``ValueError`` after a change to dtype coercion (:issue:`22702`). Datetimelike ^^^^^^^^^^^^ diff --git a/pandas/core/arrays/categorical.py b/pandas/core/arrays/categorical.py index 63a1dacb47abbe..216bccf7d63093 100644 --- a/pandas/core/arrays/categorical.py +++ b/pandas/core/arrays/categorical.py @@ -2439,9 +2439,13 @@ def _get_codes_for_values(values, categories): """ utility routine to turn values into codes given the specified categories """ - from pandas.core.algorithms import _get_data_algo, _hashtables - if not is_dtype_equal(values.dtype, categories.dtype): + if is_dtype_equal(values.dtype, categories.dtype): + # To prevent erroneous dtype coercion in _get_data_algo, retrieve + # the underlying numpy array. gh-22702 + values = getattr(values, 'values', values) + categories = getattr(categories, 'values', categories) + else: values = ensure_object(values) categories = ensure_object(categories) diff --git a/pandas/tests/arrays/categorical/test_constructors.py b/pandas/tests/arrays/categorical/test_constructors.py index b5f499ba273239..998c1182c013ad 100644 --- a/pandas/tests/arrays/categorical/test_constructors.py +++ b/pandas/tests/arrays/categorical/test_constructors.py @@ -42,6 +42,12 @@ def test_constructor_empty(self): expected = pd.Int64Index([1, 2, 3]) tm.assert_index_equal(c.categories, expected) + def test_constructor_empty_boolean(self): + # see gh-22702 + cat = pd.Categorical([], categories=[True, False]) + categories = sorted(cat.categories.tolist()) + assert categories == [False, True] + def test_constructor_tuples(self): values = np.array([(1,), (1, 2), (1,), (1, 2)], dtype=object) result = Categorical(values) diff --git a/pandas/tests/indexes/test_category.py b/pandas/tests/indexes/test_category.py index 2221fd023b561e..d49a6a6abc7c9d 100644 --- a/pandas/tests/indexes/test_category.py +++ b/pandas/tests/indexes/test_category.py @@ -136,6 +136,12 @@ def test_construction_with_dtype(self): 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 = pd.CategoricalIndex([], categories=[True, False]) + categories = sorted(cat.categories.tolist()) + assert categories == [False, True] + def test_construction_with_categorical_dtype(self): # construction with CategoricalDtype # GH18109 From e568fb090a6a5f3a03fc3beb451ffb1e7115dba4 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 20 Sep 2018 09:01:31 -0500 Subject: [PATCH 04/85] is_bool_dtype for ExtensionArrays (#22667) Closes https://github.com/pandas-dev/pandas/issues/22665 Closes https://github.com/pandas-dev/pandas/issues/22326 --- doc/source/whatsnew/v0.24.0.txt | 4 +- pandas/core/common.py | 40 ++++++- pandas/core/dtypes/base.py | 20 ++++ pandas/core/dtypes/common.py | 17 +++ pandas/core/dtypes/dtypes.py | 6 + .../tests/arrays/categorical/test_indexing.py | 27 ++++- pandas/tests/dtypes/test_dtypes.py | 14 ++- pandas/tests/extension/arrow/__init__.py | 0 pandas/tests/extension/arrow/bool.py | 108 ++++++++++++++++++ pandas/tests/extension/arrow/test_bool.py | 48 ++++++++ 10 files changed, 276 insertions(+), 8 deletions(-) create mode 100644 pandas/tests/extension/arrow/__init__.py create mode 100644 pandas/tests/extension/arrow/bool.py create mode 100644 pandas/tests/extension/arrow/test_bool.py diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index e2ba35c1ad7f9f..2f70d4e5946a06 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -485,6 +485,7 @@ ExtensionType Changes - ``ExtensionArray`` has gained the abstract methods ``.dropna()`` (:issue:`21185`) - ``ExtensionDtype`` has gained the ability to instantiate from string dtypes, e.g. ``decimal`` would instantiate a registered ``DecimalDtype``; furthermore the ``ExtensionDtype`` has gained the method ``construct_array_type`` (:issue:`21185`) +- An ``ExtensionArray`` with a boolean dtype now works correctly as a boolean indexer. :meth:`pandas.api.types.is_bool_dtype` now properly considers them boolean (:issue:`22326`) - Added ``ExtensionDtype._is_numeric`` for controlling whether an extension dtype is considered numeric (:issue:`22290`). - The ``ExtensionArray`` constructor, ``_from_sequence`` now take the keyword arg ``copy=False`` (:issue:`21185`) - Bug in :meth:`Series.get` for ``Series`` using ``ExtensionArray`` and integer index (:issue:`21257`) @@ -616,7 +617,8 @@ Categorical ^^^^^^^^^^^ - Bug in :meth:`Categorical.from_codes` where ``NaN`` values in ``codes`` were silently converted to ``0`` (:issue:`21767`). In the future this will raise a ``ValueError``. Also changes the behavior of ``.from_codes([1.1, 2.0])``. -- Constructing a :class:`pd.CategoricalIndex` with empty values and boolean categories was raising a ``ValueError`` after a change to dtype coercion (:issue:`22702`). +- Bug when indexing with a boolean-valued ``Categorical``. Now a boolean-valued ``Categorical`` is treated as a boolean mask (:issue:`22665`) +- Constructing a :class:`CategoricalIndex` with empty values and boolean categories was raising a ``ValueError`` after a change to dtype coercion (:issue:`22702`). Datetimelike ^^^^^^^^^^^^ diff --git a/pandas/core/common.py b/pandas/core/common.py index a6b05daf1d85d8..14e47936e1b505 100644 --- a/pandas/core/common.py +++ b/pandas/core/common.py @@ -15,7 +15,9 @@ from pandas import compat from pandas.compat import iteritems, PY36, OrderedDict from pandas.core.dtypes.generic import ABCSeries, ABCIndex, ABCIndexClass -from pandas.core.dtypes.common import is_integer +from pandas.core.dtypes.common import ( + is_integer, is_bool_dtype, is_extension_array_dtype, is_array_like +) from pandas.core.dtypes.inference import _iterable_not_string from pandas.core.dtypes.missing import isna, isnull, notnull # noqa from pandas.core.dtypes.cast import construct_1d_object_array_from_listlike @@ -100,17 +102,45 @@ def maybe_box_datetimelike(value): def is_bool_indexer(key): - if isinstance(key, (ABCSeries, np.ndarray, ABCIndex)): + # type: (Any) -> bool + """ + Check whether `key` is a valid boolean indexer. + + Parameters + ---------- + key : Any + Only list-likes may be considered boolean indexers. + All other types are not considered a boolean indexer. + For array-like input, boolean ndarrays or ExtensionArrays + with ``_is_boolean`` set are considered boolean indexers. + + Returns + ------- + bool + + Raises + ------ + ValueError + When the array is an object-dtype ndarray or ExtensionArray + and contains missing values. + """ + na_msg = 'cannot index with vector containing NA / NaN values' + if (isinstance(key, (ABCSeries, np.ndarray, ABCIndex)) or + (is_array_like(key) and is_extension_array_dtype(key.dtype))): if key.dtype == np.object_: key = np.asarray(values_from_object(key)) if not lib.is_bool_array(key): if isna(key).any(): - raise ValueError('cannot index with vector containing ' - 'NA / NaN values') + raise ValueError(na_msg) return False return True - elif key.dtype == np.bool_: + elif is_bool_dtype(key.dtype): + # an ndarray with bool-dtype by definition has no missing values. + # So we only need to check for NAs in ExtensionArrays + if is_extension_array_dtype(key.dtype): + if np.any(key.isna()): + raise ValueError(na_msg) return True elif isinstance(key, list): try: diff --git a/pandas/core/dtypes/base.py b/pandas/core/dtypes/base.py index 7dcdf878231f13..a552251ebbafa0 100644 --- a/pandas/core/dtypes/base.py +++ b/pandas/core/dtypes/base.py @@ -106,6 +106,25 @@ def _is_numeric(self): """ return False + @property + def _is_boolean(self): + # type: () -> bool + """ + Whether this dtype should be considered boolean. + + By default, ExtensionDtypes are assumed to be non-numeric. + Setting this to True will affect the behavior of several places, + e.g. + + * is_bool + * boolean indexing + + Returns + ------- + bool + """ + return False + class ExtensionDtype(_DtypeOpsMixin): """A custom data type, to be paired with an ExtensionArray. @@ -125,6 +144,7 @@ class ExtensionDtype(_DtypeOpsMixin): pandas operations * _is_numeric + * _is_boolean Optionally one can override construct_array_type for construction with the name of this dtype via the Registry. See diff --git a/pandas/core/dtypes/common.py b/pandas/core/dtypes/common.py index f6e7e87f1043b3..e2b9e246aee50f 100644 --- a/pandas/core/dtypes/common.py +++ b/pandas/core/dtypes/common.py @@ -1619,6 +1619,11 @@ def is_bool_dtype(arr_or_dtype): ------- boolean : Whether or not the array or dtype is of a boolean dtype. + Notes + ----- + An ExtensionArray is considered boolean when the ``_is_boolean`` + attribute is set to True. + Examples -------- >>> is_bool_dtype(str) @@ -1635,6 +1640,8 @@ def is_bool_dtype(arr_or_dtype): False >>> is_bool_dtype(np.array([True, False])) True + >>> is_bool_dtype(pd.Categorical([True, False])) + True """ if arr_or_dtype is None: @@ -1645,6 +1652,13 @@ def is_bool_dtype(arr_or_dtype): # this isn't even a dtype return False + if isinstance(arr_or_dtype, (ABCCategorical, ABCCategoricalIndex)): + arr_or_dtype = arr_or_dtype.dtype + + if isinstance(arr_or_dtype, CategoricalDtype): + arr_or_dtype = arr_or_dtype.categories + # now we use the special definition for Index + if isinstance(arr_or_dtype, ABCIndexClass): # TODO(jreback) @@ -1653,6 +1667,9 @@ def is_bool_dtype(arr_or_dtype): # guess this return (arr_or_dtype.is_object and arr_or_dtype.inferred_type == 'boolean') + elif is_extension_array_dtype(arr_or_dtype): + dtype = getattr(arr_or_dtype, 'dtype', arr_or_dtype) + return dtype._is_boolean return issubclass(tipo, np.bool_) diff --git a/pandas/core/dtypes/dtypes.py b/pandas/core/dtypes/dtypes.py index 4fd77e41a1c67d..d879ded4f0f098 100644 --- a/pandas/core/dtypes/dtypes.py +++ b/pandas/core/dtypes/dtypes.py @@ -462,6 +462,12 @@ def ordered(self): """Whether the categories have an ordered relationship""" return self._ordered + @property + def _is_boolean(self): + from pandas.core.dtypes.common import is_bool_dtype + + return is_bool_dtype(self.categories) + class DatetimeTZDtypeType(type): """ diff --git a/pandas/tests/arrays/categorical/test_indexing.py b/pandas/tests/arrays/categorical/test_indexing.py index b54ac2835bee3b..d23da1565a9528 100644 --- a/pandas/tests/arrays/categorical/test_indexing.py +++ b/pandas/tests/arrays/categorical/test_indexing.py @@ -5,7 +5,8 @@ import numpy as np import pandas.util.testing as tm -from pandas import Categorical, Index, CategoricalIndex, PeriodIndex +from pandas import Categorical, Index, CategoricalIndex, PeriodIndex, Series +import pandas.core.common as com from pandas.tests.arrays.categorical.common import TestCategorical @@ -121,3 +122,27 @@ def test_get_indexer_non_unique(self, idx_values, key_values, key_class): tm.assert_numpy_array_equal(expected, result) tm.assert_numpy_array_equal(exp_miss, res_miss) + + +@pytest.mark.parametrize("index", [True, False]) +def test_mask_with_boolean(index): + s = Series(range(3)) + idx = Categorical([True, False, True]) + if index: + idx = CategoricalIndex(idx) + + assert com.is_bool_indexer(idx) + result = s[idx] + expected = s[idx.astype('object')] + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize("index", [True, False]) +def test_mask_with_boolean_raises(index): + s = Series(range(3)) + idx = Categorical([True, False, None]) + if index: + idx = CategoricalIndex(idx) + + with tm.assert_raises_regex(ValueError, 'NA / NaN'): + s[idx] diff --git a/pandas/tests/dtypes/test_dtypes.py b/pandas/tests/dtypes/test_dtypes.py index 55c841ba1fc46b..e3d14497a38f93 100644 --- a/pandas/tests/dtypes/test_dtypes.py +++ b/pandas/tests/dtypes/test_dtypes.py @@ -17,7 +17,7 @@ is_dtype_equal, is_datetime64_ns_dtype, is_datetime64_dtype, is_interval_dtype, is_datetime64_any_dtype, is_string_dtype, - _coerce_to_dtype) + _coerce_to_dtype, is_bool_dtype) import pandas.util.testing as tm @@ -126,6 +126,18 @@ def test_tuple_categories(self): result = CategoricalDtype(categories) assert all(result.categories == categories) + @pytest.mark.parametrize("categories, expected", [ + ([True, False], True), + ([True, False, None], True), + ([True, False, "a", "b'"], False), + ([0, 1], False), + ]) + def test_is_boolean(self, categories, expected): + cat = Categorical(categories) + assert cat.dtype._is_boolean is expected + assert is_bool_dtype(cat) is expected + assert is_bool_dtype(cat.dtype) is expected + class TestDatetimeTZDtype(Base): diff --git a/pandas/tests/extension/arrow/__init__.py b/pandas/tests/extension/arrow/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/pandas/tests/extension/arrow/bool.py b/pandas/tests/extension/arrow/bool.py new file mode 100644 index 00000000000000..a9da25cdd27558 --- /dev/null +++ b/pandas/tests/extension/arrow/bool.py @@ -0,0 +1,108 @@ +"""Rudimentary Apache Arrow-backed ExtensionArray. + +At the moment, just a boolean array / type is implemented. +Eventually, we'll want to parametrize the type and support +multiple dtypes. Not all methods are implemented yet, and the +current implementation is not efficient. +""" +import copy +import itertools + +import numpy as np +import pyarrow as pa +import pandas as pd +from pandas.api.extensions import ( + ExtensionDtype, ExtensionArray, take, register_extension_dtype +) + + +@register_extension_dtype +class ArrowBoolDtype(ExtensionDtype): + + type = np.bool_ + kind = 'b' + name = 'arrow_bool' + na_value = pa.NULL + + @classmethod + def construct_from_string(cls, string): + if string == cls.name: + return cls() + else: + raise TypeError("Cannot construct a '{}' from " + "'{}'".format(cls, string)) + + @classmethod + def construct_array_type(cls): + return ArrowBoolArray + + def _is_boolean(self): + return True + + +class ArrowBoolArray(ExtensionArray): + def __init__(self, values): + if not isinstance(values, pa.ChunkedArray): + raise ValueError + + assert values.type == pa.bool_() + self._data = values + self._dtype = ArrowBoolDtype() + + def __repr__(self): + return "ArrowBoolArray({})".format(repr(self._data)) + + @classmethod + def from_scalars(cls, values): + arr = pa.chunked_array([pa.array(np.asarray(values))]) + return cls(arr) + + @classmethod + def from_array(cls, arr): + assert isinstance(arr, pa.Array) + return cls(pa.chunked_array([arr])) + + @classmethod + def _from_sequence(cls, scalars, dtype=None, copy=False): + return cls.from_scalars(scalars) + + def __getitem__(self, item): + return self._data.to_pandas()[item] + + def __len__(self): + return len(self._data) + + @property + def dtype(self): + return self._dtype + + @property + def nbytes(self): + return sum(x.size for chunk in self._data.chunks + for x in chunk.buffers() + if x is not None) + + def isna(self): + return pd.isna(self._data.to_pandas()) + + def take(self, indices, allow_fill=False, fill_value=None): + data = self._data.to_pandas() + + if allow_fill and fill_value is None: + fill_value = self.dtype.na_value + + result = take(data, indices, fill_value=fill_value, + allow_fill=allow_fill) + return self._from_sequence(result, dtype=self.dtype) + + def copy(self, deep=False): + if deep: + return copy.deepcopy(self._data) + else: + return copy.copy(self._data) + + def _concat_same_type(cls, to_concat): + chunks = list(itertools.chain.from_iterable(x._data.chunks + for x in to_concat)) + arr = pa.chunked_array(chunks) + return cls(arr) diff --git a/pandas/tests/extension/arrow/test_bool.py b/pandas/tests/extension/arrow/test_bool.py new file mode 100644 index 00000000000000..e1afedcade3ff7 --- /dev/null +++ b/pandas/tests/extension/arrow/test_bool.py @@ -0,0 +1,48 @@ +import numpy as np +import pytest +import pandas as pd +import pandas.util.testing as tm +from pandas.tests.extension import base + +pytest.importorskip('pyarrow', minversion="0.10.0") + +from .bool import ArrowBoolDtype, ArrowBoolArray + + +@pytest.fixture +def dtype(): + return ArrowBoolDtype() + + +@pytest.fixture +def data(): + return ArrowBoolArray.from_scalars(np.random.randint(0, 2, size=100, + dtype=bool)) + + +class BaseArrowTests(object): + pass + + +class TestDtype(BaseArrowTests, base.BaseDtypeTests): + def test_array_type_with_arg(self, data, dtype): + pytest.skip("GH-22666") + + +class TestInterface(BaseArrowTests, base.BaseInterfaceTests): + def test_repr(self, data): + raise pytest.skip("TODO") + + +class TestConstructors(BaseArrowTests, base.BaseConstructorsTests): + def test_from_dtype(self, data): + pytest.skip("GH-22666") + + +def test_is_bool_dtype(data): + assert pd.api.types.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) From 793b24e2051b2bab2d35b2a07fb7ae3306cba5b5 Mon Sep 17 00:00:00 2001 From: Diego Argueta Date: Thu, 20 Sep 2018 07:31:14 -0700 Subject: [PATCH 05/85] DOC: Fix outdated default values in util.testing docstrings (#22776) --- pandas/util/testing.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pandas/util/testing.py b/pandas/util/testing.py index edd0b0aa82d234..3db251e89842d5 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -225,7 +225,7 @@ def assert_almost_equal(left, right, check_dtype="equiv", ---------- left : object right : object - check_dtype : bool / string {'equiv'}, default False + check_dtype : bool / string {'equiv'}, default 'equiv' Check dtype if both a and b are the same type. If 'equiv' is passed in, then `RangeIndex` and `Int64Index` are also considered equivalent when doing type checking. @@ -787,7 +787,7 @@ def assert_index_equal(left, right, exact='equiv', check_names=True, ---------- left : Index right : Index - exact : bool / string {'equiv'}, default False + exact : bool / string {'equiv'}, default 'equiv' Whether to check the Index class, dtype and inferred_type are identical. If 'equiv', then RangeIndex can be substituted for Int64Index as well. @@ -1034,7 +1034,7 @@ def assert_interval_array_equal(left, right, exact='equiv', Whether to check the Index class, dtype and inferred_type are identical. If 'equiv', then RangeIndex can be substituted for Int64Index as well. - obj : str, default 'Categorical' + obj : str, default 'IntervalArray' Specify object name being compared, internally used to show appropriate assertion message """ @@ -1326,12 +1326,13 @@ def assert_frame_equal(left, right, check_dtype=True, Second DataFrame to compare. check_dtype : bool, default True Whether to check the DataFrame dtype is identical. - check_index_type : {'equiv'} or bool, default 'equiv' + check_index_type : bool / string {'equiv'}, default 'equiv' Whether to check the Index class, dtype and inferred_type are identical. - check_column_type : {'equiv'} or bool, default 'equiv' + check_column_type : bool / string {'equiv'}, default 'equiv' Whether to check the columns class, dtype and inferred_type - are identical. + are identical. Is passed as the ``exact`` argument of + :func:`assert_index_equal`. check_frame_type : bool, default True Whether to check the DataFrame class is identical. check_less_precise : bool or int, default False From 32a74f19b655f4b0ef198d5ff4e15d409c3670d2 Mon Sep 17 00:00:00 2001 From: James Bourbeau Date: Thu, 20 Sep 2018 09:52:52 -0500 Subject: [PATCH 06/85] DOC: Reorders DataFrame.any and all docstrings (#22774) --- pandas/core/generic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 3f7334131e1467..75baeab402734d 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -9671,15 +9671,15 @@ def _doc_parms(cls): original index. * None : reduce all axes, return a scalar. +bool_only : boolean, default None + Include only boolean columns. If None, will attempt to use everything, + then use only boolean data. Not implemented for Series. skipna : boolean, default True Exclude NA/null values. If an entire row/column is NA, the result will be NA. level : int or level name, default None If the axis is a MultiIndex (hierarchical), count along a particular level, collapsing into a %(name1)s. -bool_only : boolean, default None - Include only boolean columns. If None, will attempt to use everything, - then use only boolean data. Not implemented for Series. **kwargs : any, default None Additional keywords have no effect but might be accepted for compatibility with NumPy. From 0480f4c183a95712cb8ceaf5682c5b8dd02e0f21 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 20 Sep 2018 11:22:45 -0500 Subject: [PATCH 07/85] ENH: _is_homogeneous (#22780) --- pandas/core/base.py | 15 +++++++++++++ pandas/core/frame.py | 28 ++++++++++++++++++++++++ pandas/core/indexes/multi.py | 20 +++++++++++++++++ pandas/tests/frame/test_dtypes.py | 24 ++++++++++++++++++++ pandas/tests/indexing/test_multiindex.py | 8 +++++++ pandas/tests/series/test_dtypes.py | 5 +++++ 6 files changed, 100 insertions(+) diff --git a/pandas/core/base.py b/pandas/core/base.py index d831dc69338bd0..26fea89b45ae1f 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -663,6 +663,21 @@ def transpose(self, *args, **kwargs): T = property(transpose, doc="return the transpose, which is by " "definition self") + @property + def _is_homogeneous(self): + """Whether the object has a single dtype. + + By definition, Series and Index are always considered homogeneous. + A MultiIndex may or may not be homogeneous, depending on the + dtypes of the levels. + + See Also + -------- + DataFrame._is_homogeneous + MultiIndex._is_homogeneous + """ + return True + @property def shape(self): """ return a tuple of the shape of the underlying data """ diff --git a/pandas/core/frame.py b/pandas/core/frame.py index bb221ced9e6bdd..959b0a4fd18903 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -613,6 +613,34 @@ def shape(self): """ return len(self.index), len(self.columns) + @property + def _is_homogeneous(self): + """ + Whether all the columns in a DataFrame have the same type. + + Returns + ------- + bool + + Examples + -------- + >>> DataFrame({"A": [1, 2], "B": [3, 4]})._is_homogeneous + True + >>> DataFrame({"A": [1, 2], "B": [3.0, 4.0]})._is_homogeneous + False + + Items with the same type but different sizes are considered + different types. + + >>> DataFrame({"A": np.array([1, 2], dtype=np.int32), + ... "B": np.array([1, 2], dtype=np.int64)})._is_homogeneous + False + """ + if self._data.any_extension_types: + return len({block.dtype for block in self._data.blocks}) == 1 + else: + return not self._data.is_mixed_type + def _repr_fits_vertical_(self): """ Check length against max_rows. diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index a7932f667f6de7..ad38f037b6578d 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -288,6 +288,26 @@ def _verify_integrity(self, labels=None, levels=None): def levels(self): return self._levels + @property + def _is_homogeneous(self): + """Whether the levels of a MultiIndex all have the same dtype. + + This looks at the dtypes of the levels. + + See Also + -------- + Index._is_homogeneous + DataFrame._is_homogeneous + + Examples + -------- + >>> MultiIndex.from_tuples([('a', 'b'), ('a', 'c')])._is_homogeneous + True + >>> MultiIndex.from_tuples([('a', 1), ('a', 2)])._is_homogeneous + False + """ + return len({x.dtype for x in self.levels}) <= 1 + def _set_levels(self, levels, level=None, copy=False, validate=True, verify_integrity=False): # This is NOT part of the levels property because it should be diff --git a/pandas/tests/frame/test_dtypes.py b/pandas/tests/frame/test_dtypes.py index 3b3ab3d03dce9b..ca4bd64659e069 100644 --- a/pandas/tests/frame/test_dtypes.py +++ b/pandas/tests/frame/test_dtypes.py @@ -815,6 +815,30 @@ def test_constructor_list_str_na(self, string_dtype): expected = DataFrame({"A": ['1.0', '2.0', None]}, dtype=object) assert_frame_equal(result, expected) + @pytest.mark.parametrize("data, expected", [ + # empty + (DataFrame(), True), + # multi-same + (DataFrame({"A": [1, 2], "B": [1, 2]}), True), + # multi-object + (DataFrame({"A": np.array([1, 2], dtype=object), + "B": np.array(["a", "b"], dtype=object)}), True), + # multi-extension + (DataFrame({"A": pd.Categorical(['a', 'b']), + "B": pd.Categorical(['a', 'b'])}), True), + # differ types + (DataFrame({"A": [1, 2], "B": [1., 2.]}), False), + # differ sizes + (DataFrame({"A": np.array([1, 2], dtype=np.int32), + "B": np.array([1, 2], dtype=np.int64)}), False), + # multi-extension differ + (DataFrame({"A": pd.Categorical(['a', 'b']), + "B": pd.Categorical(['b', 'c'])}), False), + + ]) + def test_is_homogeneous(self, data, expected): + assert data._is_homogeneous is expected + class TestDataFrameDatetimeWithTZ(TestData): diff --git a/pandas/tests/indexing/test_multiindex.py b/pandas/tests/indexing/test_multiindex.py index 9e66dfad3ddc7d..aefa8badf72e79 100644 --- a/pandas/tests/indexing/test_multiindex.py +++ b/pandas/tests/indexing/test_multiindex.py @@ -733,6 +733,14 @@ def test_multiindex_contains_dropped(self): assert 'a' in idx.levels[0] assert 'a' not in idx + @pytest.mark.parametrize("data, expected", [ + (MultiIndex.from_product([(), ()]), True), + (MultiIndex.from_product([(1, 2), (3, 4)]), True), + (MultiIndex.from_product([('a', 'b'), (1, 2)]), False), + ]) + def test_multiindex_is_homogeneous(self, data, expected): + assert data._is_homogeneous is expected + class TestMultiIndexSlicers(object): diff --git a/pandas/tests/series/test_dtypes.py b/pandas/tests/series/test_dtypes.py index 7aecaf340a3e0e..83a458eedbd93a 100644 --- a/pandas/tests/series/test_dtypes.py +++ b/pandas/tests/series/test_dtypes.py @@ -508,3 +508,8 @@ def test_infer_objects_series(self): assert actual.dtype == 'object' tm.assert_series_equal(actual, expected) + + def test_is_homogeneous(self): + assert Series()._is_homogeneous + assert Series([1, 2])._is_homogeneous + assert Series(pd.Categorical([1, 2]))._is_homogeneous From 8a1164ce10fafbafad2b2b5ea4e608b29d677e91 Mon Sep 17 00:00:00 2001 From: alimcmaster1 Date: Thu, 20 Sep 2018 22:21:09 +0100 Subject: [PATCH 08/85] Enforce E741 (#22795) --- .pep8speaks.yml | 1 - setup.cfg | 1 - 2 files changed, 2 deletions(-) diff --git a/.pep8speaks.yml b/.pep8speaks.yml index fda26d87bf7f67..cd610907007ebb 100644 --- a/.pep8speaks.yml +++ b/.pep8speaks.yml @@ -8,5 +8,4 @@ pycodestyle: ignore: # Errors and warnings to ignore - E402, # module level import not at top of file - E731, # do not assign a lambda expression, use a def - - E741, # do not use variables named 'l', 'O', or 'I' - W503 # line break before binary operator diff --git a/setup.cfg b/setup.cfg index fb42dfd3b6d151..e4a2357def4741 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,7 +15,6 @@ parentdir_prefix = pandas- ignore = E402, # module level import not at top of file E731, # do not assign a lambda expression, use a def - E741, # do not use variables named 'l', 'O', or 'I' W503, # line break before binary operator C405, # Unnecessary (list/tuple) literal - rewrite as a set literal. C406, # Unnecessary (list/tuple) literal - rewrite as a dict literal. From f8c5705f0bf0063acec0ff45b10404f38893a5fa Mon Sep 17 00:00:00 2001 From: dannyhyunkim <38394262+dannyhyunkim@users.noreply.github.com> Date: Fri, 21 Sep 2018 08:17:20 +1000 Subject: [PATCH 09/85] ENH: Making header_style a property of ExcelFormatter #22758 (#22759) --- pandas/io/formats/excel.py | 45 ++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 0bc268bc18b957..d6fcfb2207cf95 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -34,15 +34,6 @@ def __init__(self, row, col, val, style=None, mergestart=None, self.mergeend = mergeend -header_style = {"font": {"bold": True}, - "borders": {"top": "thin", - "right": "thin", - "bottom": "thin", - "left": "thin"}, - "alignment": {"horizontal": "center", - "vertical": "top"}} - - class CSSToExcelConverter(object): """A callable for converting CSS declarations to ExcelWriter styles @@ -389,6 +380,16 @@ def __init__(self, df, na_rep='', float_format=None, cols=None, self.merge_cells = merge_cells self.inf_rep = inf_rep + @property + def header_style(self): + return {"font": {"bold": True}, + "borders": {"top": "thin", + "right": "thin", + "bottom": "thin", + "left": "thin"}, + "alignment": {"horizontal": "center", + "vertical": "top"}} + def _format_value(self, val): if is_scalar(val) and missing.isna(val): val = self.na_rep @@ -427,7 +428,7 @@ def _format_header_mi(self): # Format multi-index as a merged cells. for lnum in range(len(level_lengths)): name = columns.names[lnum] - yield ExcelCell(lnum, coloffset, name, header_style) + yield ExcelCell(lnum, coloffset, name, self.header_style) for lnum, (spans, levels, labels) in enumerate(zip( level_lengths, columns.levels, columns.labels)): @@ -435,16 +436,16 @@ def _format_header_mi(self): for i in spans: if spans[i] > 1: yield ExcelCell(lnum, coloffset + i + 1, values[i], - header_style, lnum, + self.header_style, lnum, coloffset + i + spans[i]) else: yield ExcelCell(lnum, coloffset + i + 1, values[i], - header_style) + self.header_style) else: # Format in legacy format with dots to indicate levels. for i, values in enumerate(zip(*level_strs)): v = ".".join(map(pprint_thing, values)) - yield ExcelCell(lnum, coloffset + i + 1, v, header_style) + yield ExcelCell(lnum, coloffset + i + 1, v, self.header_style) self.rowcounter = lnum @@ -469,7 +470,7 @@ def _format_header_regular(self): for colindex, colname in enumerate(colnames): yield ExcelCell(self.rowcounter, colindex + coloffset, colname, - header_style) + self.header_style) def _format_header(self): if isinstance(self.columns, ABCMultiIndex): @@ -482,7 +483,8 @@ def _format_header(self): row = [x if x is not None else '' for x in self.df.index.names] + [''] * len(self.columns) if reduce(lambda x, y: x and y, map(lambda x: x != '', row)): - gen2 = (ExcelCell(self.rowcounter, colindex, val, header_style) + gen2 = (ExcelCell(self.rowcounter, colindex, val, + self.header_style) for colindex, val in enumerate(row)) self.rowcounter += 1 return itertools.chain(gen, gen2) @@ -518,7 +520,7 @@ def _format_regular_rows(self): if index_label and self.header is not False: yield ExcelCell(self.rowcounter - 1, 0, index_label, - header_style) + self.header_style) # write index_values index_values = self.df.index @@ -526,7 +528,8 @@ def _format_regular_rows(self): index_values = self.df.index.to_timestamp() for idx, idxval in enumerate(index_values): - yield ExcelCell(self.rowcounter + idx, 0, idxval, header_style) + yield ExcelCell(self.rowcounter + idx, 0, idxval, + self.header_style) coloffset = 1 else: @@ -562,7 +565,7 @@ def _format_hierarchical_rows(self): for cidx, name in enumerate(index_labels): yield ExcelCell(self.rowcounter - 1, cidx, name, - header_style) + self.header_style) if self.merge_cells: # Format hierarchical rows as merged cells. @@ -581,12 +584,12 @@ def _format_hierarchical_rows(self): for i in spans: if spans[i] > 1: yield ExcelCell(self.rowcounter + i, gcolidx, - values[i], header_style, + values[i], self.header_style, self.rowcounter + i + spans[i] - 1, gcolidx) else: yield ExcelCell(self.rowcounter + i, gcolidx, - values[i], header_style) + values[i], self.header_style) gcolidx += 1 else: @@ -594,7 +597,7 @@ def _format_hierarchical_rows(self): for indexcolvals in zip(*self.df.index): for idx, indexcolval in enumerate(indexcolvals): yield ExcelCell(self.rowcounter + idx, gcolidx, - indexcolval, header_style) + indexcolval, self.header_style) gcolidx += 1 for cell in self._generate_body(gcolidx): From 4612a828244725dab1ff928b71cf92d04b40cd04 Mon Sep 17 00:00:00 2001 From: Armin Varshokar Date: Thu, 20 Sep 2018 18:18:30 -0400 Subject: [PATCH 10/85] API/DEPR: 'periods' argument instead of 'n' for DatetimeIndex.shift() (#22697) --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/core/arrays/datetimelike.py | 39 +++++++++++++++------- pandas/core/generic.py | 5 +++ pandas/tests/indexes/datetimes/test_ops.py | 10 ++++++ 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 2f70d4e5946a06..135e97f309d7ea 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -562,6 +562,7 @@ Deprecations - :meth:`Series.str.cat` has deprecated using arbitrary list-likes *within* list-likes. A list-like container may still contain many ``Series``, ``Index`` or 1-dimensional ``np.ndarray``, or alternatively, only scalar values. (:issue:`21950`) - :meth:`FrozenNDArray.searchsorted` has deprecated the ``v`` parameter in favor of ``value`` (:issue:`14645`) +- :func:`DatetimeIndex.shift` now accepts ``periods`` argument instead of ``n`` for consistency with :func:`Index.shift` and :func:`Series.shift`. Using ``n`` throws a deprecation warning (:issue:`22458`) .. _whatsnew_0240.prior_deprecations: diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 69925ce1c520e4..91c119808db520 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -38,6 +38,7 @@ from pandas.core.algorithms import checked_add_with_arr from .base import ExtensionOpsMixin +from pandas.util._decorators import deprecate_kwarg def _make_comparison_op(op, cls): @@ -522,40 +523,54 @@ def _addsub_offset_array(self, other, op): kwargs['freq'] = 'infer' return type(self)(res_values, **kwargs) - def shift(self, n, freq=None): + @deprecate_kwarg(old_arg_name='n', new_arg_name='periods') + def shift(self, periods, freq=None): """ - Specialized shift which produces a Datetime/Timedelta Array/Index + 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 ---------- - n : int - Periods to shift by - freq : DateOffset or timedelta-like, optional + periods : int + Number of periods (or increments) to shift by, + can be positive or negative. + + .. versionchanged:: 0.24.0 + + 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 ------- - shifted : same type as self + pandas.DatetimeIndex + Shifted index. + + See Also + -------- + Index.shift : Shift values of Index. """ if freq is not None and freq != self.freq: if isinstance(freq, compat.string_types): freq = frequencies.to_offset(freq) - offset = n * freq + offset = periods * freq result = self + offset - if hasattr(self, 'tz'): result._tz = self.tz - return result - if n == 0: + if periods == 0: # immutable so OK return self.copy() if self.freq is None: raise NullFrequencyError("Cannot shift with no freq") - start = self[0] + n * self.freq - end = self[-1] + n * self.freq + start = self[0] + periods * self.freq + end = self[-1] + periods * self.freq attribs = self._get_attributes_dict() return self._generate_range(start=start, end=end, periods=None, **attribs) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 75baeab402734d..b72d8cbf02bc60 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -8288,6 +8288,11 @@ def mask(self, cond, other=np.nan, inplace=False, axis=None, level=None, See Notes. axis : %(axes_single_arg)s + See Also + -------- + Index.shift : Shift values of Index. + DatetimeIndex.shift : Shift values of DatetimeIndex. + Notes ----- If freq is specified then the index values are shifted but the data diff --git a/pandas/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py index 24d99abaf44a85..b60b222d095b99 100644 --- a/pandas/tests/indexes/datetimes/test_ops.py +++ b/pandas/tests/indexes/datetimes/test_ops.py @@ -540,6 +540,16 @@ def test_shift(self): shifted = rng.shift(1, freq=CDay()) assert shifted[0] == rng[0] + CDay() + def test_shift_periods(self): + # GH #22458 : argument 'n' was deprecated in favor of 'periods' + idx = pd.DatetimeIndex(start=START, end=END, + periods=3) + tm.assert_index_equal(idx.shift(periods=0), idx) + tm.assert_index_equal(idx.shift(0), idx) + with tm.assert_produces_warning(FutureWarning, + check_stacklevel=True): + tm.assert_index_equal(idx.shift(n=0), idx) + def test_pickle_unpickle(self): unpickled = tm.round_trip_pickle(self.rng) assert unpickled.freq is not None From bdb7a1603f1e0948ca0cab011987f616e7296167 Mon Sep 17 00:00:00 2001 From: Diego Argueta Date: Fri, 21 Sep 2018 01:17:59 -0700 Subject: [PATCH 11/85] ENH: Add support for excluding the index from Parquet files (GH20768) (#22266) --- doc/source/io.rst | 38 +++++++++++++++++++++++++++++++++ doc/source/whatsnew/v0.24.0.txt | 4 ++++ pandas/core/frame.py | 11 ++++++++-- pandas/io/parquet.py | 33 ++++++++++++++++++++-------- pandas/tests/io/test_parquet.py | 34 +++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 11 deletions(-) diff --git a/doc/source/io.rst b/doc/source/io.rst index c2c8c1c17700f3..cb22bb9198e258 100644 --- a/doc/source/io.rst +++ b/doc/source/io.rst @@ -4570,6 +4570,9 @@ dtypes, including extension dtypes such as datetime with tz. Several caveats. * Duplicate column names and non-string columns names are not supported. +* The ``pyarrow`` engine always writes the index to the output, but ``fastparquet`` only writes non-default + indexes. This extra column can cause problems for non-Pandas consumers that are not expecting it. You can + force including or omitting indexes with the ``index`` argument, regardless of the underlying engine. * Index level names, if specified, must be strings. * Categorical dtypes can be serialized to parquet, but will de-serialize as ``object`` dtype. * Non supported types include ``Period`` and actual Python object types. These will raise a helpful error message @@ -4633,6 +4636,41 @@ Read only certain columns of a parquet file. os.remove('example_pa.parquet') os.remove('example_fp.parquet') + +Handling Indexes +'''''''''''''''' + +Serializing a ``DataFrame`` to parquet may include the implicit index as one or +more columns in the output file. Thus, this code: + +.. ipython:: python + + df = pd.DataFrame({'a': [1, 2], 'b': [3, 4]}) + df.to_parquet('test.parquet', engine='pyarrow') + +creates a parquet file with *three* columns if you use ``pyarrow`` for serialization: +``a``, ``b``, and ``__index_level_0__``. If you're using ``fastparquet``, the +index `may or may not `_ +be written to the file. + +This unexpected extra column causes some databases like Amazon Redshift to reject +the file, because that column doesn't exist in the target table. + +If you want to omit a dataframe's indexes when writing, pass ``index=False`` to +:func:`~pandas.DataFrame.to_parquet`: + +.. ipython:: python + + df.to_parquet('test.parquet', index=False) + +This creates a parquet file with just the two expected columns, ``a`` and ``b``. +If your ``DataFrame`` has a custom index, you won't get it back when you load +this file into a ``DataFrame``. + +Passing ``index=True`` will *always* write the index, even if that's not the +underlying engine's default behavior. + + .. _io.sql: SQL Queries diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 135e97f309d7ea..ed1bf0a4f83941 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -17,6 +17,10 @@ New features - ``ExcelWriter`` now accepts ``mode`` as a keyword argument, enabling append to existing workbooks when using the ``openpyxl`` engine (:issue:`3441`) +- :func:`DataFrame.to_parquet` now accepts ``index`` as an argument, allowing +the user to override the engine's default behavior to include or omit the +dataframe's indexes from the resulting Parquet file. (:issue:`20768`) + .. _whatsnew_0240.enhancements.extension_array_operators: ``ExtensionArray`` operator support diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 959b0a4fd18903..0099f705fe1e1c 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -1902,7 +1902,7 @@ def to_feather(self, fname): to_feather(self, fname) def to_parquet(self, fname, engine='auto', compression='snappy', - **kwargs): + index=None, **kwargs): """ Write a DataFrame to the binary parquet format. @@ -1924,6 +1924,13 @@ def to_parquet(self, fname, engine='auto', compression='snappy', 'pyarrow' is unavailable. compression : {'snappy', 'gzip', 'brotli', None}, default 'snappy' Name of the compression to use. Use ``None`` for no compression. + index : bool, default None + If ``True``, include the dataframe's index(es) in the file output. + If ``False``, they will not be written to the file. If ``None``, + the behavior depends on the chosen engine. + + .. versionadded:: 0.24.0 + **kwargs Additional arguments passed to the parquet library. See :ref:`pandas io ` for more details. @@ -1952,7 +1959,7 @@ def to_parquet(self, fname, engine='auto', compression='snappy', """ from pandas.io.parquet import to_parquet to_parquet(self, fname, engine, - compression=compression, **kwargs) + compression=compression, index=index, **kwargs) @Substitution(header='Write out the column names. If a list of strings ' 'is given, it is assumed to be aliases for the ' diff --git a/pandas/io/parquet.py b/pandas/io/parquet.py index a99014f07a6b32..6ab56c68a510ae 100644 --- a/pandas/io/parquet.py +++ b/pandas/io/parquet.py @@ -103,19 +103,27 @@ def __init__(self): self.api = pyarrow def write(self, df, path, compression='snappy', - coerce_timestamps='ms', **kwargs): + coerce_timestamps='ms', index=None, **kwargs): self.validate_dataframe(df) - if self._pyarrow_lt_070: + + # Only validate the index if we're writing it. + if self._pyarrow_lt_070 and index is not False: self._validate_write_lt_070(df) path, _, _, _ = get_filepath_or_buffer(path, mode='wb') + if index is None: + from_pandas_kwargs = {} + else: + from_pandas_kwargs = {'preserve_index': index} + if self._pyarrow_lt_060: - table = self.api.Table.from_pandas(df, timestamps_to_ms=True) + table = self.api.Table.from_pandas(df, timestamps_to_ms=True, + **from_pandas_kwargs) self.api.parquet.write_table( table, path, compression=compression, **kwargs) else: - table = self.api.Table.from_pandas(df) + table = self.api.Table.from_pandas(df, **from_pandas_kwargs) self.api.parquet.write_table( table, path, compression=compression, coerce_timestamps=coerce_timestamps, **kwargs) @@ -197,7 +205,7 @@ def __init__(self): ) self.api = fastparquet - def write(self, df, path, compression='snappy', **kwargs): + def write(self, df, path, compression='snappy', index=None, **kwargs): self.validate_dataframe(df) # thriftpy/protocol/compact.py:339: # DeprecationWarning: tostring() is deprecated. @@ -214,8 +222,8 @@ def write(self, df, path, compression='snappy', **kwargs): path, _, _, _ = get_filepath_or_buffer(path) with catch_warnings(record=True): - self.api.write(path, df, - compression=compression, **kwargs) + self.api.write(path, df, compression=compression, + write_index=index, **kwargs) def read(self, path, columns=None, **kwargs): if is_s3_url(path): @@ -234,7 +242,8 @@ def read(self, path, columns=None, **kwargs): return parquet_file.to_pandas(columns=columns, **kwargs) -def to_parquet(df, path, engine='auto', compression='snappy', **kwargs): +def to_parquet(df, path, engine='auto', compression='snappy', index=None, + **kwargs): """ Write a DataFrame to the parquet format. @@ -250,11 +259,17 @@ def to_parquet(df, path, engine='auto', compression='snappy', **kwargs): 'pyarrow' is unavailable. compression : {'snappy', 'gzip', 'brotli', None}, default 'snappy' Name of the compression to use. Use ``None`` for no compression. + index : bool, default None + If ``True``, include the dataframe's index(es) in the file output. If + ``False``, they will not be written to the file. If ``None``, the + engine's default behavior will be used. + + .. versionadded 0.24.0 kwargs Additional keyword arguments passed to the engine """ impl = get_engine(engine) - return impl.write(df, path, compression=compression, **kwargs) + return impl.write(df, path, compression=compression, index=index, **kwargs) def read_parquet(path, engine='auto', columns=None, **kwargs): diff --git a/pandas/tests/io/test_parquet.py b/pandas/tests/io/test_parquet.py index fefbe8afb59cbf..ab7f04ad86ffc0 100644 --- a/pandas/tests/io/test_parquet.py +++ b/pandas/tests/io/test_parquet.py @@ -368,6 +368,40 @@ def test_multiindex_with_columns(self, pa_ge_070): check_round_trip(df, engine, read_kwargs={'columns': ['A', 'B']}, expected=df[['A', 'B']]) + def test_write_ignoring_index(self, engine): + # 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, 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, 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': [i for i in range(8)], + 'two': [-i for i in range(8)]}, index=arrays) + + expected = df.reset_index(drop=True) + check_round_trip(df, engine, write_kwargs=write_kwargs, + expected=expected) + class TestParquetPyArrow(Base): From fb784caf51a37ffd9ec3662ade1f5e9cdebefd17 Mon Sep 17 00:00:00 2001 From: aeltanawy Date: Sat, 22 Sep 2018 16:36:22 -0700 Subject: [PATCH 12/85] DOC: Updated the DataFrame.assign docstring (#21917) --- ci/doctests.sh | 2 +- pandas/core/frame.py | 70 ++++++++++++++++++-------------------------- 2 files changed, 29 insertions(+), 43 deletions(-) diff --git a/ci/doctests.sh b/ci/doctests.sh index e7fe80e60eb6d3..48774a1e4d00de 100755 --- a/ci/doctests.sh +++ b/ci/doctests.sh @@ -21,7 +21,7 @@ if [ "$DOCTEST" ]; then # DataFrame / Series docstrings pytest --doctest-modules -v pandas/core/frame.py \ - -k"-assign -axes -combine -isin -itertuples -join -nlargest -nsmallest -nunique -pivot_table -quantile -query -reindex -reindex_axis -replace -round -set_index -stack -to_dict -to_stata" + -k"-axes -combine -isin -itertuples -join -nlargest -nsmallest -nunique -pivot_table -quantile -query -reindex -reindex_axis -replace -round -set_index -stack -to_dict -to_stata" if [ $? -ne "0" ]; then RET=1 diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 0099f705fe1e1c..81d5c112885ec8 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -3280,7 +3280,7 @@ def assign(self, **kwargs): Parameters ---------- - kwargs : keyword, value pairs + **kwargs : dict of {str: callable or Series} The column names are keywords. If the values are callable, they are computed on the DataFrame and assigned to the new columns. The callable must not @@ -3290,7 +3290,7 @@ def assign(self, **kwargs): Returns ------- - df : DataFrame + DataFrame A new DataFrame with the new columns in addition to all the existing columns. @@ -3310,48 +3310,34 @@ def assign(self, **kwargs): Examples -------- - >>> df = pd.DataFrame({'A': range(1, 11), 'B': np.random.randn(10)}) + >>> df = pd.DataFrame({'temp_c': [17.0, 25.0]}, + ... index=['Portland', 'Berkeley']) + >>> df + temp_c + Portland 17.0 + Berkeley 25.0 Where the value is a callable, evaluated on `df`: - - >>> df.assign(ln_A = lambda x: np.log(x.A)) - A B ln_A - 0 1 0.426905 0.000000 - 1 2 -0.780949 0.693147 - 2 3 -0.418711 1.098612 - 3 4 -0.269708 1.386294 - 4 5 -0.274002 1.609438 - 5 6 -0.500792 1.791759 - 6 7 1.649697 1.945910 - 7 8 -1.495604 2.079442 - 8 9 0.549296 2.197225 - 9 10 -0.758542 2.302585 - - Where the value already exists and is inserted: - - >>> newcol = np.log(df['A']) - >>> df.assign(ln_A=newcol) - A B ln_A - 0 1 0.426905 0.000000 - 1 2 -0.780949 0.693147 - 2 3 -0.418711 1.098612 - 3 4 -0.269708 1.386294 - 4 5 -0.274002 1.609438 - 5 6 -0.500792 1.791759 - 6 7 1.649697 1.945910 - 7 8 -1.495604 2.079442 - 8 9 0.549296 2.197225 - 9 10 -0.758542 2.302585 - - Where the keyword arguments depend on each other - - >>> df = pd.DataFrame({'A': [1, 2, 3]}) - - >>> df.assign(B=df.A, C=lambda x:x['A']+ x['B']) - A B C - 0 1 1 2 - 1 2 2 4 - 2 3 3 6 + >>> df.assign(temp_f=lambda x: x.temp_c * 9 / 5 + 32) + temp_c temp_f + Portland 17.0 62.6 + Berkeley 25.0 77.0 + + Alternatively, the same behavior can be achieved by directly + referencing an existing Series or sequence: + >>> df.assign(temp_f=df['temp_c'] * 9 / 5 + 32) + temp_c temp_f + Portland 17.0 62.6 + Berkeley 25.0 77.0 + + In Python 3.6+, you can create multiple columns within the same assign + where one of the columns depends on another one defined within the same + assign: + >>> df.assign(temp_f=lambda x: x['temp_c'] * 9 / 5 + 32, + ... temp_k=lambda x: (x['temp_f'] + 459.67) * 5 / 9) + temp_c temp_f temp_k + Portland 17.0 62.6 290.15 + Berkeley 25.0 77.0 298.15 """ data = self.copy() From f65fa755db23c8010d60f2160c522264417cb545 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Sun, 23 Sep 2018 05:11:01 -0700 Subject: [PATCH 13/85] BUG: Avoid AmbiguousTime or NonExistentTime Error when resampling (#22809) --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/core/resample.py | 36 +++++++++++++++------------------ pandas/tests/test_resample.py | 16 +++++++++++++++ 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index ed1bf0a4f83941..31ef70703e2cab 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -679,6 +679,7 @@ Timezones - Bug when setting a new value with :meth:`DataFrame.loc` with a :class:`DatetimeIndex` with a DST transition (:issue:`18308`, :issue:`20724`) - Bug in :meth:`DatetimeIndex.unique` that did not re-localize tz-aware dates correctly (:issue:`21737`) - Bug when indexing a :class:`Series` with a DST transition (:issue:`21846`) +- Bug in :meth:`DataFrame.resample` and :meth:`Series.resample` where an ``AmbiguousTimeError`` or ``NonExistentTimeError`` would raise if a timezone aware timeseries ended on a DST transition (:issue:`19375`, :issue:`10117`) Offsets ^^^^^^^ diff --git a/pandas/core/resample.py b/pandas/core/resample.py index 1ef8a0854887bc..878ac957a8557f 100644 --- a/pandas/core/resample.py +++ b/pandas/core/resample.py @@ -1328,8 +1328,7 @@ def _get_time_bins(self, ax): data=[], freq=self.freq, name=ax.name) return binner, [], labels - first, last = ax.min(), ax.max() - first, last = _get_range_edges(first, last, self.freq, + first, last = _get_range_edges(ax.min(), ax.max(), self.freq, closed=self.closed, base=self.base) tz = ax.tz @@ -1519,9 +1518,6 @@ def _take_new_index(obj, indexer, new_index, axis=0): def _get_range_edges(first, last, offset, closed='left', base=0): - if isinstance(offset, compat.string_types): - offset = to_offset(offset) - if isinstance(offset, Tick): is_day = isinstance(offset, Day) day_nanos = delta_to_nanoseconds(timedelta(1)) @@ -1531,8 +1527,7 @@ def _get_range_edges(first, last, offset, closed='left', base=0): return _adjust_dates_anchored(first, last, offset, closed=closed, base=base) - if not isinstance(offset, Tick): # and first.time() != last.time(): - # hack! + else: first = first.normalize() last = last.normalize() @@ -1553,19 +1548,16 @@ def _adjust_dates_anchored(first, last, offset, closed='right', base=0): # # See https://github.com/pandas-dev/pandas/issues/8683 - # 14682 - Since we need to drop the TZ information to perform - # the adjustment in the presence of a DST change, - # save TZ Info and the DST state of the first and last parameters - # so that we can accurately rebuild them at the end. + # GH 10117 & GH 19375. If first and last contain timezone information, + # Perform the calculation in UTC in order to avoid localizing on an + # Ambiguous or Nonexistent time. first_tzinfo = first.tzinfo last_tzinfo = last.tzinfo - first_dst = bool(first.dst()) - last_dst = bool(last.dst()) - - first = first.tz_localize(None) - last = last.tz_localize(None) - start_day_nanos = first.normalize().value + if first_tzinfo is not None: + first = first.tz_convert('UTC') + if last_tzinfo is not None: + last = last.tz_convert('UTC') base_nanos = (base % offset.n) * offset.nanos // offset.n start_day_nanos += base_nanos @@ -1598,9 +1590,13 @@ def _adjust_dates_anchored(first, last, offset, closed='right', base=0): lresult = last.value + (offset.nanos - loffset) else: lresult = last.value + offset.nanos - - return (Timestamp(fresult).tz_localize(first_tzinfo, ambiguous=first_dst), - Timestamp(lresult).tz_localize(last_tzinfo, ambiguous=last_dst)) + fresult = Timestamp(fresult) + lresult = Timestamp(lresult) + if first_tzinfo is not None: + fresult = fresult.tz_localize('UTC').tz_convert(first_tzinfo) + if last_tzinfo is not None: + lresult = lresult.tz_localize('UTC').tz_convert(last_tzinfo) + return fresult, lresult def asfreq(obj, freq, method=None, how=None, normalize=False, fill_value=None): diff --git a/pandas/tests/test_resample.py b/pandas/tests/test_resample.py index 377253574d2c1d..ccd2461d1512e9 100644 --- a/pandas/tests/test_resample.py +++ b/pandas/tests/test_resample.py @@ -2485,6 +2485,22 @@ def test_with_local_timezone_dateutil(self): expected = Series(1, index=expected_index) 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='15T') + s = Series(np.zeros(len(index)), index=index) + expected = s.tz_localize('US/Pacific') + result = expected.resample('900S').mean() + tm.assert_series_equal(result, expected) + + def test_resample_ambiguous_time_bin_edge(self): + # GH 10117 + idx = pd.date_range("2014-10-25 22:00:00", "2014-10-26 00:30:00", + freq="30T", tz="Europe/London") + expected = Series(np.zeros(len(idx)), index=idx) + result = expected.resample('30T').mean() + tm.assert_series_equal(result, expected) + def test_fill_method_and_how_upsample(self): # GH2073 s = Series(np.arange(9, dtype='int64'), From 945bf75103f3465a5d937a661a0baf9bc156db6b Mon Sep 17 00:00:00 2001 From: Anjali2019 Date: Sun, 23 Sep 2018 14:37:41 +0200 Subject: [PATCH 14/85] TST: Fixturize series/test_asof.py (#22772) --- pandas/tests/series/test_asof.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pandas/tests/series/test_asof.py b/pandas/tests/series/test_asof.py index 3104d85601434c..e85a0ac42ae1a4 100644 --- a/pandas/tests/series/test_asof.py +++ b/pandas/tests/series/test_asof.py @@ -8,10 +8,8 @@ import pandas.util.testing as tm -from .common import TestData - -class TestSeriesAsof(TestData): +class TestSeriesAsof(): def test_basic(self): From f67b90ded3c55a641e1e3d8afb007811f69da372 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Sun, 23 Sep 2018 06:25:41 -0700 Subject: [PATCH 15/85] BUG/ENH: Handle AmbiguousTimeError in date rounding (#22647) --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/_libs/tslibs/nattype.pyx | 29 ++++++ pandas/_libs/tslibs/timestamps.pyx | 45 +++++++-- pandas/core/indexes/datetimelike.py | 35 +++++-- .../tests/scalar/timestamp/test_unary_ops.py | 22 +++++ pandas/tests/series/test_datetime_values.py | 95 ++++++++++++------- 6 files changed, 173 insertions(+), 54 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 31ef70703e2cab..28d0c33851ae47 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -186,6 +186,7 @@ Other Enhancements - :func:`to_timedelta` now supports iso-formated timedelta strings (:issue:`21877`) - :class:`Series` and :class:`DataFrame` now support :class:`Iterable` in constructor (:issue:`2193`) - :class:`DatetimeIndex` gained :attr:`DatetimeIndex.timetz` attribute. Returns local time with timezone information. (:issue:`21358`) +- :meth:`round`, :meth:`ceil`, and meth:`floor` for :class:`DatetimeIndex` and :class:`Timestamp` now support an ``ambiguous`` argument for handling datetimes that are rounded to ambiguous times (:issue:`18946`) - :class:`Resampler` now is iterable like :class:`GroupBy` (:issue:`15314`). - :meth:`Series.resample` and :meth:`DataFrame.resample` have gained the :meth:`Resampler.quantile` (:issue:`15023`). - :meth:`Index.to_frame` now supports overriding column name(s) (:issue:`22580`). diff --git a/pandas/_libs/tslibs/nattype.pyx b/pandas/_libs/tslibs/nattype.pyx index fd8486f690745b..ae4f9c821b5d12 100644 --- a/pandas/_libs/tslibs/nattype.pyx +++ b/pandas/_libs/tslibs/nattype.pyx @@ -477,6 +477,13 @@ class NaTType(_NaT): Parameters ---------- freq : a freq string indicating the rounding resolution + ambiguous : bool, 'NaT', default 'raise' + - bool contains flags to determine if time is dst or not (note + that this flag is only applicable for ambiguous fall dst dates) + - 'NaT' will return NaT for an ambiguous time + - 'raise' will raise an AmbiguousTimeError for an ambiguous time + + .. versionadded:: 0.24.0 Raises ------ @@ -489,6 +496,17 @@ class NaTType(_NaT): Parameters ---------- freq : a freq string indicating the flooring resolution + ambiguous : bool, 'NaT', default 'raise' + - bool contains flags to determine if time is dst or not (note + that this flag is only applicable for ambiguous fall dst dates) + - 'NaT' will return NaT for an ambiguous time + - 'raise' will raise an AmbiguousTimeError for an ambiguous time + + .. versionadded:: 0.24.0 + + Raises + ------ + ValueError if the freq cannot be converted """) ceil = _make_nat_func('ceil', # noqa:E128 """ @@ -497,6 +515,17 @@ class NaTType(_NaT): Parameters ---------- freq : a freq string indicating the ceiling resolution + ambiguous : bool, 'NaT', default 'raise' + - bool contains flags to determine if time is dst or not (note + that this flag is only applicable for ambiguous fall dst dates) + - 'NaT' will return NaT for an ambiguous time + - 'raise' will raise an AmbiguousTimeError for an ambiguous time + + .. versionadded:: 0.24.0 + + Raises + ------ + ValueError if the freq cannot be converted """) tz_convert = _make_nat_func('tz_convert', # noqa:E128 diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 52343593d1cc16..e985a519c3046d 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -656,7 +656,7 @@ class Timestamp(_Timestamp): return create_timestamp_from_ts(ts.value, ts.dts, ts.tzinfo, freq) - def _round(self, freq, rounder): + def _round(self, freq, rounder, ambiguous='raise'): if self.tz is not None: value = self.tz_localize(None).value else: @@ -668,10 +668,10 @@ class Timestamp(_Timestamp): r = round_ns(value, rounder, freq)[0] result = Timestamp(r, unit='ns') if self.tz is not None: - result = result.tz_localize(self.tz) + result = result.tz_localize(self.tz, ambiguous=ambiguous) return result - def round(self, freq): + def round(self, freq, ambiguous='raise'): """ Round the Timestamp to the specified resolution @@ -682,32 +682,61 @@ class Timestamp(_Timestamp): Parameters ---------- freq : a freq string indicating the rounding resolution + ambiguous : bool, 'NaT', default 'raise' + - bool contains flags to determine if time is dst or not (note + that this flag is only applicable for ambiguous fall dst dates) + - 'NaT' will return NaT for an ambiguous time + - 'raise' will raise an AmbiguousTimeError for an ambiguous time + + .. versionadded:: 0.24.0 Raises ------ ValueError if the freq cannot be converted """ - return self._round(freq, np.round) + return self._round(freq, np.round, ambiguous) - def floor(self, freq): + def floor(self, freq, ambiguous='raise'): """ return a new Timestamp floored to this resolution Parameters ---------- freq : a freq string indicating the flooring resolution + ambiguous : bool, 'NaT', default 'raise' + - bool contains flags to determine if time is dst or not (note + that this flag is only applicable for ambiguous fall dst dates) + - 'NaT' will return NaT for an ambiguous time + - 'raise' will raise an AmbiguousTimeError for an ambiguous time + + .. versionadded:: 0.24.0 + + Raises + ------ + ValueError if the freq cannot be converted """ - return self._round(freq, np.floor) + return self._round(freq, np.floor, ambiguous) - def ceil(self, freq): + def ceil(self, freq, ambiguous='raise'): """ return a new Timestamp ceiled to this resolution Parameters ---------- freq : a freq string indicating the ceiling resolution + ambiguous : bool, 'NaT', default 'raise' + - bool contains flags to determine if time is dst or not (note + that this flag is only applicable for ambiguous fall dst dates) + - 'NaT' will return NaT for an ambiguous time + - 'raise' will raise an AmbiguousTimeError for an ambiguous time + + .. versionadded:: 0.24.0 + + Raises + ------ + ValueError if the freq cannot be converted """ - return self._round(freq, np.ceil) + return self._round(freq, np.ceil, ambiguous) @property def tz(self): diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 3f8c07fe7cd217..578167a7db5009 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -99,6 +99,18 @@ class TimelikeOps(object): frequency like 'S' (second) not 'ME' (month end). See :ref:`frequency aliases ` for a list of possible `freq` values. + ambiguous : 'infer', bool-ndarray, 'NaT', default 'raise' + - 'infer' will attempt to infer fall dst-transition hours based on + order + - bool-ndarray where True signifies a DST time, False designates + 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 an AmbiguousTimeError if there are ambiguous + times + Only relevant for DatetimeIndex + + .. versionadded:: 0.24.0 Returns ------- @@ -168,7 +180,7 @@ class TimelikeOps(object): """ ) - def _round(self, freq, rounder): + def _round(self, freq, rounder, ambiguous): # round the local times values = _ensure_datetimelike_to_i8(self) result = round_ns(values, rounder, freq) @@ -180,19 +192,20 @@ def _round(self, freq, rounder): if 'tz' in attribs: attribs['tz'] = None return self._ensure_localized( - self._shallow_copy(result, **attribs)) + self._shallow_copy(result, **attribs), ambiguous + ) @Appender((_round_doc + _round_example).format(op="round")) - def round(self, freq, *args, **kwargs): - return self._round(freq, np.round) + def round(self, freq, ambiguous='raise'): + return self._round(freq, np.round, ambiguous) @Appender((_round_doc + _floor_example).format(op="floor")) - def floor(self, freq): - return self._round(freq, np.floor) + def floor(self, freq, ambiguous='raise'): + return self._round(freq, np.floor, ambiguous) @Appender((_round_doc + _ceil_example).format(op="ceil")) - def ceil(self, freq): - return self._round(freq, np.ceil) + def ceil(self, freq, ambiguous='raise'): + return self._round(freq, np.ceil, ambiguous) class DatetimeIndexOpsMixin(DatetimeLikeArrayMixin): @@ -264,7 +277,7 @@ def _evaluate_compare(self, other, op): except TypeError: return result - def _ensure_localized(self, result): + def _ensure_localized(self, result, ambiguous='raise'): """ ensure that we are re-localized @@ -274,6 +287,8 @@ def _ensure_localized(self, result): Parameters ---------- result : DatetimeIndex / i8 ndarray + ambiguous : str, bool, or bool-ndarray + default 'raise' Returns ------- @@ -284,7 +299,7 @@ def _ensure_localized(self, result): if getattr(self, 'tz', None) is not None: if not isinstance(result, ABCIndexClass): result = self._simple_new(result) - result = result.tz_localize(self.tz) + result = result.tz_localize(self.tz, ambiguous=ambiguous) return result def _box_values_as_index(self): diff --git a/pandas/tests/scalar/timestamp/test_unary_ops.py b/pandas/tests/scalar/timestamp/test_unary_ops.py index bf41840c58dedf..f83aa31edf95a1 100644 --- a/pandas/tests/scalar/timestamp/test_unary_ops.py +++ b/pandas/tests/scalar/timestamp/test_unary_ops.py @@ -132,6 +132,28 @@ def test_floor(self): expected = Timestamp('20130101') assert result == expected + @pytest.mark.parametrize('method', ['ceil', 'round', 'floor']) + def test_round_dst_border(self, method): + # GH 18946 round near DST + ts = Timestamp('2017-10-29 00:00:00', tz='UTC').tz_convert( + 'Europe/Madrid' + ) + # + result = getattr(ts, method)('H', ambiguous=True) + assert result == ts + + result = getattr(ts, method)('H', ambiguous=False) + expected = Timestamp('2017-10-29 01:00:00', tz='UTC').tz_convert( + 'Europe/Madrid' + ) + assert result == expected + + result = getattr(ts, method)('H', ambiguous='NaT') + assert result is NaT + + with pytest.raises(pytz.AmbiguousTimeError): + getattr(ts, method)('H', ambiguous='raise') + # -------------------------------------------------------------- # Timestamp.replace diff --git a/pandas/tests/series/test_datetime_values.py b/pandas/tests/series/test_datetime_values.py index 5b45c6003a005b..fee2323310b9c5 100644 --- a/pandas/tests/series/test_datetime_values.py +++ b/pandas/tests/series/test_datetime_values.py @@ -5,6 +5,7 @@ import calendar import unicodedata import pytest +import pytz from datetime import datetime, time, date @@ -95,42 +96,6 @@ def compare(s, name): expected = Series(exp_values, index=s.index, name='xxx') tm.assert_series_equal(result, expected) - # round - s = 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 = s.dt.round('D') - expected = Series(pd.to_datetime(['2012-01-02', '2012-01-02', - '2012-01-01']), name='xxx') - tm.assert_series_equal(result, expected) - - # round with tz - result = (s.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) - - # floor - s = 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 = s.dt.floor('D') - expected = Series(pd.to_datetime(['2012-01-01', '2012-01-01', - '2012-01-01']), name='xxx') - tm.assert_series_equal(result, expected) - - # ceil - s = 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 = s.dt.ceil('D') - expected = Series(pd.to_datetime(['2012-01-02', '2012-01-02', - '2012-01-02']), name='xxx') - tm.assert_series_equal(result, expected) - # datetimeindex with tz s = Series(date_range('20130101', periods=5, tz='US/Eastern'), name='xxx') @@ -261,6 +226,64 @@ def get_dir(s): with pytest.raises(com.SettingWithCopyError): s.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 + s = 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(s.dt, method)('D') + expected = Series(pd.to_datetime(dates), name='xxx') + tm.assert_series_equal(result, expected) + + def test_dt_round_tz(self): + s = 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 = (s.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 DST + df1 = pd.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 pytest.raises(pytz.AmbiguousTimeError): + getattr(df1.date.dt, method)('H', ambiguous='raise') + def test_dt_namespace_accessor_categorical(self): # GH 19468 dti = DatetimeIndex(['20171111', '20181212']).repeat(2) From e5a99c65fb98deaab5ced62cc25cabd4970bcfc4 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Sun, 23 Sep 2018 06:35:16 -0700 Subject: [PATCH 16/85] Fix Series v Index bool ops (#22173) --- doc/source/whatsnew/v0.24.0.txt | 2 - pandas/core/ops.py | 58 ++++++++++++++----------- pandas/tests/series/test_operators.py | 61 ++++++++++++++++----------- 3 files changed, 70 insertions(+), 51 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 28d0c33851ae47..618d7454c67fe5 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -819,5 +819,3 @@ Other - :meth:`~pandas.io.formats.style.Styler.bar` now also supports tablewise application (in addition to rowwise and columnwise) with ``axis=None`` and setting clipping range with ``vmin`` and ``vmax`` (:issue:`21548` and :issue:`21526`). ``NaN`` values are also handled properly. - Logical operations ``&, |, ^`` between :class:`Series` and :class:`Index` will no longer raise ``ValueError`` (:issue:`22092`) - -- -- diff --git a/pandas/core/ops.py b/pandas/core/ops.py index a7fc2839ea1017..70fe7de0a973e7 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -1525,23 +1525,22 @@ def _bool_method_SERIES(cls, op, special): Wrapper function for Series arithmetic operations, to avoid code duplication. """ + op_name = _get_op_name(op, special) def na_op(x, y): try: result = op(x, y) except TypeError: - if isinstance(y, list): - y = construct_1d_object_array_from_listlike(y) - - if isinstance(y, (np.ndarray, ABCSeries, ABCIndexClass)): - if (is_bool_dtype(x.dtype) and is_bool_dtype(y.dtype)): - result = op(x, y) # when would this be hit? - else: - x = ensure_object(x) - y = ensure_object(y) - result = libops.vec_binop(x, y, op) + assert not isinstance(y, (list, ABCSeries, ABCIndexClass)) + if isinstance(y, np.ndarray): + # bool-bool dtype operations should be OK, should not get here + assert not (is_bool_dtype(x) and is_bool_dtype(y)) + x = ensure_object(x) + y = ensure_object(y) + result = libops.vec_binop(x, y, op) else: # let null fall thru + assert lib.is_scalar(y) if not isna(y): y = bool(y) try: @@ -1561,33 +1560,42 @@ def wrapper(self, other): is_self_int_dtype = is_integer_dtype(self.dtype) self, other = _align_method_SERIES(self, other, align_asobject=True) + res_name = get_op_result_name(self, other) if isinstance(other, ABCDataFrame): # Defer to DataFrame implementation; fail early return NotImplemented - elif isinstance(other, ABCSeries): - name = get_op_result_name(self, other) + elif isinstance(other, (ABCSeries, ABCIndexClass)): is_other_int_dtype = is_integer_dtype(other.dtype) other = fill_int(other) if is_other_int_dtype else fill_bool(other) - filler = (fill_int if is_self_int_dtype and is_other_int_dtype - else fill_bool) - - res_values = na_op(self.values, other.values) - unfilled = self._constructor(res_values, - index=self.index, name=name) - return filler(unfilled) + ovalues = other.values + finalizer = lambda x: x else: # scalars, list, tuple, np.array - filler = (fill_int if is_self_int_dtype and - is_integer_dtype(np.asarray(other)) else fill_bool) - - res_values = na_op(self.values, other) - unfilled = self._constructor(res_values, index=self.index) - return filler(unfilled).__finalize__(self) + is_other_int_dtype = is_integer_dtype(np.asarray(other)) + if is_list_like(other) and not isinstance(other, np.ndarray): + # TODO: Can we do this before the is_integer_dtype check? + # could the is_integer_dtype check be checking the wrong + # thing? e.g. other = [[0, 1], [2, 3], [4, 5]]? + other = construct_1d_object_array_from_listlike(other) + + ovalues = other + finalizer = lambda x: x.__finalize__(self) + + # For int vs int `^`, `|`, `&` are bitwise operators and return + # integer dtypes. Otherwise these are boolean ops + filler = (fill_int if is_self_int_dtype and is_other_int_dtype + else fill_bool) + res_values = na_op(self.values, ovalues) + unfilled = self._constructor(res_values, + index=self.index, name=res_name) + filled = filler(unfilled) + return finalizer(filled) + wrapper.__name__ = op_name return wrapper diff --git a/pandas/tests/series/test_operators.py b/pandas/tests/series/test_operators.py index 615f0c9247bd89..601e251d45b4b4 100644 --- a/pandas/tests/series/test_operators.py +++ b/pandas/tests/series/test_operators.py @@ -14,6 +14,7 @@ NaT, date_range, timedelta_range, Categorical) from pandas.core.indexes.datetimes import Timestamp import pandas.core.nanops as nanops +from pandas.core import ops from pandas.compat import range from pandas import compat @@ -425,30 +426,6 @@ def test_comparison_flex_alignment_fill(self): exp = pd.Series([True, True, False, False], index=list('abcd')) assert_series_equal(left.gt(right, fill_value=0), exp) - def test_logical_ops_with_index(self): - # GH22092 - ser = Series([True, True, False, False]) - idx1 = Index([True, False, True, False]) - idx2 = Index([1, 0, 1, 0]) - - expected = Series([True, False, False, False]) - result1 = ser & idx1 - assert_series_equal(result1, expected) - result2 = ser & idx2 - assert_series_equal(result2, expected) - - expected = Series([True, True, True, False]) - result1 = ser | idx1 - assert_series_equal(result1, expected) - result2 = ser | idx2 - assert_series_equal(result2, expected) - - expected = Series([False, True, True, False]) - result1 = ser ^ idx1 - assert_series_equal(result1, expected) - result2 = ser ^ idx2 - assert_series_equal(result2, expected) - def test_ne(self): ts = Series([3, 4, 5, 6, 7], [3, 4, 5, 6, 7], dtype=float) expected = [True, True, False, True, True] @@ -627,6 +604,42 @@ def test_ops_datetimelike_align(self): result = (dt2.to_frame() - dt.to_frame())[0] assert_series_equal(result, expected) + @pytest.mark.parametrize('op', [ + operator.and_, + operator.or_, + operator.xor, + pytest.param(ops.rand_, + marks=pytest.mark.xfail(reason="GH#22092 Index " + "implementation returns " + "Index", + raises=AssertionError, + strict=True)), + pytest.param(ops.ror_, + marks=pytest.mark.xfail(reason="GH#22092 Index " + "implementation raises", + raises=ValueError, strict=True)), + pytest.param(ops.rxor, + marks=pytest.mark.xfail(reason="GH#22092 Index " + "implementation raises", + raises=TypeError, strict=True)) + ]) + def test_bool_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) + assert_series_equal(result, expected) + + expected = Series([op(ser[n], idx2[n]) for n in range(len(ser))], + dtype=bool) + + result = op(ser, idx2) + assert_series_equal(result, expected) + def test_operators_bitwise(self): # GH 9016: support bitwise op for integer types index = list('bca') From 27de8e692f7bf3c2efdd9c2d49ec589c29fd74be Mon Sep 17 00:00:00 2001 From: Anjali2019 Date: Sun, 23 Sep 2018 15:42:14 +0200 Subject: [PATCH 17/85] TST: Fixturize series/test_apply.py (#22769) --- pandas/tests/series/test_apply.py | 99 ++++++++++++++++--------------- 1 file changed, 50 insertions(+), 49 deletions(-) diff --git a/pandas/tests/series/test_apply.py b/pandas/tests/series/test_apply.py index b717d75d835d03..20215279cf0311 100644 --- a/pandas/tests/series/test_apply.py +++ b/pandas/tests/series/test_apply.py @@ -17,18 +17,18 @@ import pandas.util.testing as tm from pandas.conftest import _get_cython_table_params -from .common import TestData +class TestSeriesApply(): -class TestSeriesApply(TestData): - - def test_apply(self): + def test_apply(self, datetime_series): with np.errstate(all='ignore'): - tm.assert_series_equal(self.ts.apply(np.sqrt), np.sqrt(self.ts)) + tm.assert_series_equal(datetime_series.apply(np.sqrt), + np.sqrt(datetime_series)) # element-wise apply import math - tm.assert_series_equal(self.ts.apply(math.exp), np.exp(self.ts)) + tm.assert_series_equal(datetime_series.apply(math.exp), + np.exp(datetime_series)) # empty series s = Series(dtype=object, name='foo', index=pd.Index([], name='bar')) @@ -66,11 +66,11 @@ def test_apply_dont_convert_dtype(self): result = s.apply(f, convert_dtype=False) assert result.dtype == object - def test_with_string_args(self): + def test_with_string_args(self, datetime_series): for arg in ['sum', 'mean', 'min', 'max', 'std']: - result = self.ts.apply(arg) - expected = getattr(self.ts, arg)() + result = datetime_series.apply(arg) + expected = getattr(datetime_series, arg)() assert result == expected def test_apply_args(self): @@ -165,34 +165,34 @@ def test_apply_dict_depr(self): tsdf.A.agg({'foo': ['sum', 'mean']}) -class TestSeriesAggregate(TestData): +class TestSeriesAggregate(): - def test_transform(self): + def test_transform(self, string_series): # transforming functions with np.errstate(all='ignore'): - f_sqrt = np.sqrt(self.series) - f_abs = np.abs(self.series) + f_sqrt = np.sqrt(string_series) + f_abs = np.abs(string_series) # ufunc - result = self.series.transform(np.sqrt) + result = string_series.transform(np.sqrt) expected = f_sqrt.copy() assert_series_equal(result, expected) - result = self.series.apply(np.sqrt) + result = string_series.apply(np.sqrt) assert_series_equal(result, expected) # list-like - result = self.series.transform([np.sqrt]) + result = string_series.transform([np.sqrt]) expected = f_sqrt.to_frame().copy() expected.columns = ['sqrt'] assert_frame_equal(result, expected) - result = self.series.transform([np.sqrt]) + result = string_series.transform([np.sqrt]) assert_frame_equal(result, expected) - result = self.series.transform(['sqrt']) + result = string_series.transform(['sqrt']) assert_frame_equal(result, expected) # multiple items in list @@ -200,10 +200,10 @@ def test_transform(self): # series and then concatting expected = pd.concat([f_sqrt, f_abs], axis=1) expected.columns = ['sqrt', 'absolute'] - result = self.series.apply([np.sqrt, np.abs]) + result = string_series.apply([np.sqrt, np.abs]) assert_frame_equal(result, expected) - result = self.series.transform(['sqrt', 'abs']) + result = string_series.transform(['sqrt', 'abs']) expected.columns = ['sqrt', 'abs'] assert_frame_equal(result, expected) @@ -212,28 +212,28 @@ def test_transform(self): expected.columns = ['foo', 'bar'] expected = expected.unstack().rename('series') - result = self.series.apply({'foo': np.sqrt, 'bar': np.abs}) + result = string_series.apply({'foo': np.sqrt, 'bar': np.abs}) assert_series_equal(result.reindex_like(expected), expected) - def test_transform_and_agg_error(self): + def test_transform_and_agg_error(self, string_series): # we are trying to transform with an aggregator def f(): - self.series.transform(['min', 'max']) + string_series.transform(['min', 'max']) pytest.raises(ValueError, f) def f(): with np.errstate(all='ignore'): - self.series.agg(['sqrt', 'max']) + string_series.agg(['sqrt', 'max']) pytest.raises(ValueError, f) def f(): with np.errstate(all='ignore'): - self.series.transform(['sqrt', 'max']) + string_series.transform(['sqrt', 'max']) pytest.raises(ValueError, f) def f(): with np.errstate(all='ignore'): - self.series.agg({'foo': np.sqrt, 'bar': 'sum'}) + string_series.agg({'foo': np.sqrt, 'bar': 'sum'}) pytest.raises(ValueError, f) def test_demo(self): @@ -272,33 +272,34 @@ def test_multiple_aggregators_with_dict_api(self): 'min', 'sum']).unstack().rename('series') tm.assert_series_equal(result.reindex_like(expected), expected) - def test_agg_apply_evaluate_lambdas_the_same(self): + def test_agg_apply_evaluate_lambdas_the_same(self, string_series): # test that we are evaluating row-by-row first # before vectorized evaluation - result = self.series.apply(lambda x: str(x)) - expected = self.series.agg(lambda x: str(x)) + result = string_series.apply(lambda x: str(x)) + expected = string_series.agg(lambda x: str(x)) tm.assert_series_equal(result, expected) - result = self.series.apply(str) - expected = self.series.agg(str) + result = string_series.apply(str) + expected = string_series.agg(str) tm.assert_series_equal(result, expected) - def test_with_nested_series(self): + def test_with_nested_series(self, datetime_series): # GH 2316 # .agg with a reducer and a transform, what to do - result = self.ts.apply(lambda x: Series( + result = datetime_series.apply(lambda x: Series( [x, x ** 2], index=['x', 'x^2'])) - expected = DataFrame({'x': self.ts, 'x^2': self.ts ** 2}) + expected = DataFrame({'x': datetime_series, + 'x^2': datetime_series ** 2}) tm.assert_frame_equal(result, expected) - result = self.ts.agg(lambda x: Series( + result = datetime_series.agg(lambda x: Series( [x, x ** 2], index=['x', 'x^2'])) tm.assert_frame_equal(result, expected) - def test_replicate_describe(self): + def test_replicate_describe(self, string_series): # this also tests a result set that is all scalars - expected = self.series.describe() - result = self.series.apply(OrderedDict( + expected = string_series.describe() + result = string_series.apply(OrderedDict( [('count', 'count'), ('mean', 'mean'), ('std', 'std'), @@ -309,13 +310,13 @@ def test_replicate_describe(self): ('max', 'max')])) assert_series_equal(result, expected) - def test_reduce(self): + def test_reduce(self, string_series): # reductions with named functions - result = self.series.agg(['sum', 'mean']) - expected = Series([self.series.sum(), - self.series.mean()], + result = string_series.agg(['sum', 'mean']) + expected = Series([string_series.sum(), + string_series.mean()], ['sum', 'mean'], - name=self.series.name) + name=string_series.name) assert_series_equal(result, expected) def test_non_callable_aggregates(self): @@ -414,9 +415,9 @@ def test_agg_cython_table_raises(self, series, func, expected): series.agg(func) -class TestSeriesMap(TestData): +class TestSeriesMap(): - def test_map(self): + def test_map(self, datetime_series): index, data = tm.getMixedTypeDict() source = Series(data['B'], index=data['C']) @@ -434,8 +435,8 @@ def test_map(self): assert v == source[target[k]] # function - result = self.ts.map(lambda x: x * 2) - tm.assert_series_equal(result, self.ts * 2) + result = datetime_series.map(lambda x: x * 2) + tm.assert_series_equal(result, datetime_series * 2) # GH 10324 a = Series([1, 2, 3, 4]) @@ -500,10 +501,10 @@ def test_map_type_inference(self): s2 = s.map(lambda x: np.where(x == 0, 0, 1)) assert issubclass(s2.dtype.type, np.integer) - def test_map_decimal(self): + def test_map_decimal(self, string_series): from decimal import Decimal - result = self.series.map(lambda x: Decimal(str(x))) + result = string_series.map(lambda x: Decimal(str(x))) assert result.dtype == np.object_ assert isinstance(result[0], Decimal) From 4cc0a71c729aaf9a5cf46a2c4b3fd5e0a4fd37a9 Mon Sep 17 00:00:00 2001 From: Thierry Moisan Date: Sun, 23 Sep 2018 11:32:57 -0400 Subject: [PATCH 18/85] DOC: Fix DataFrame.to_csv docstring and add an example. GH22459 (#22475) --- pandas/core/generic.py | 116 ++++++++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 48 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index b72d8cbf02bc60..9d19b02c4d1fb8 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -9501,80 +9501,100 @@ def to_csv(self, path_or_buf=None, sep=",", na_rep='', float_format=None, quotechar='"', line_terminator='\n', chunksize=None, tupleize_cols=None, date_format=None, doublequote=True, escapechar=None, decimal='.'): - r"""Write object to a comma-separated values (csv) file + r""" + Write object to a comma-separated values (csv) file. + + .. versionchanged:: 0.24.0 + The order of arguments for Series was changed. Parameters ---------- - path_or_buf : string or file handle, default None + path_or_buf : str or file handle, default None File path or object, if None is provided the result is returned as a string. .. versionchanged:: 0.24.0 Was previously named "path" for Series. - sep : character, default ',' - Field delimiter for the output file. - na_rep : string, default '' - Missing data representation - float_format : string, default None - Format string for floating point numbers + sep : str, default ',' + String of length 1. Field delimiter for the output file. + na_rep : str, default '' + Missing data representation. + float_format : str, default None + Format string for floating point numbers. columns : sequence, optional - Columns to write - header : boolean or list of string, default True + Columns to write. + header : bool or list of str, default True Write out the column names. If a list of strings is given it is - assumed to be aliases for the column names + assumed to be aliases for the column names. .. versionchanged:: 0.24.0 Previously defaulted to False for Series. - index : boolean, default True - Write row names (index) - index_label : string or sequence, or False, default None + index : bool, default True + Write row names (index). + index_label : str or sequence, or False, default None Column label for index column(s) if desired. If None is given, and `header` and `index` are True, then the index names are used. A - sequence should be given if the object uses MultiIndex. If + sequence should be given if the object uses MultiIndex. If False do not print fields for index names. Use index_label=False - for easier importing in R + for easier importing in R. mode : str - Python write mode, default 'w' - encoding : string, optional + Python write mode, default 'w'. + encoding : str, optional A string representing the encoding to use in the output file, defaults to 'ascii' on Python 2 and 'utf-8' on Python 3. - compression : {'infer', 'gzip', 'bz2', 'zip', 'xz', None}, - default 'infer' - If 'infer' and `path_or_buf` is path-like, then detect compression - from the following extensions: '.gz', '.bz2', '.zip' or '.xz' - (otherwise no compression). - + compression : str, default 'infer' + Compression mode among the following possible values: {'infer', + 'gzip', 'bz2', 'zip', 'xz', None}. If 'infer' and `path_or_buf` + is path-like, then detect compression from the following + extensions: '.gz', '.bz2', '.zip' or '.xz'. (otherwise no + compression). .. versionchanged:: 0.24.0 - 'infer' option added and set to default - line_terminator : string, default ``'\n'`` - The newline character or character sequence to use in the output - file + 'infer' option added and set to default. quoting : optional constant from csv module - defaults to csv.QUOTE_MINIMAL. If you have set a `float_format` + Defaults to csv.QUOTE_MINIMAL. If you have set a `float_format` then floats are converted to strings and thus csv.QUOTE_NONNUMERIC - will treat them as non-numeric - quotechar : string (length 1), default '\"' - character used to quote fields - doublequote : boolean, default True - Control quoting of `quotechar` inside a field - escapechar : string (length 1), default None - character used to escape `sep` and `quotechar` when appropriate + will treat them as non-numeric. + quotechar : str, default '\"' + String of length 1. Character used to quote fields. + line_terminator : string, default ``'\n'`` + The newline character or character sequence to use in the output + file. chunksize : int or None - rows to write at a time - tupleize_cols : boolean, default False - .. deprecated:: 0.21.0 - This argument will be removed and will always write each row - of the multi-index as a separate row in the CSV file. - + Rows to write at a time. + tupleize_cols : bool, default False Write MultiIndex columns as a list of tuples (if True) or in the new, expanded format, where each MultiIndex column is a row in the CSV (if False). - date_format : string, default None - Format string for datetime objects - decimal: string, default '.' + .. deprecated:: 0.21.0 + This argument will be removed and will always write each row + of the multi-index as a separate row in the CSV file. + date_format : str, default None + Format string for datetime objects. + doublequote : bool, default True + Control quoting of `quotechar` inside a field. + escapechar : str, default None + String of length 1. Character used to escape `sep` and `quotechar` + when appropriate. + decimal : str, default '.' Character recognized as decimal separator. E.g. use ',' for - European data + European data. - .. versionchanged:: 0.24.0 - The order of arguments for Series was changed. + Returns + ------- + None or str + If path_or_buf is None, returns the resulting csv format as a + string. Otherwise returns None. + + See Also + -------- + pandas.read_csv : Load a CSV file into a DataFrame. + pandas.to_excel: Load an Excel file into a DataFrame. + + Examples + -------- + >>> df = pd.DataFrame({'name': ['Raphael', 'Donatello'], + ... 'mask': ['red', 'purple'], + ... 'weapon': ['sai', 'bo staff']}) + >>> df.to_csv(index=False) + 'name,mask,weapon\nRaphael,red,sai\nDonatello,purple,bo staff\n' """ df = self if isinstance(self, ABCDataFrame) else self.to_frame() From 44a9b167bda9654ce60588cf2dcee88e4bad831d Mon Sep 17 00:00:00 2001 From: h-vetinari <33685575+h-vetinari@users.noreply.github.com> Date: Sun, 23 Sep 2018 20:28:40 +0200 Subject: [PATCH 19/85] TST/CLN: Fixturize tests/frame/test_apply (#22735) --- pandas/tests/frame/test_apply.py | 362 +++++++++++++++---------------- 1 file changed, 172 insertions(+), 190 deletions(-) diff --git a/pandas/tests/frame/test_apply.py b/pandas/tests/frame/test_apply.py index 7b71240a34b5c5..e27115cfc255b4 100644 --- a/pandas/tests/frame/test_apply.py +++ b/pandas/tests/frame/test_apply.py @@ -23,25 +23,36 @@ assert_frame_equal) import pandas.util.testing as tm from pandas.conftest import _get_cython_table_params -from pandas.tests.frame.common import TestData -class TestDataFrameApply(TestData): +@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 + + +class TestDataFrameApply(): - def test_apply(self): + def test_apply(self, float_frame): with np.errstate(all='ignore'): # ufunc - applied = self.frame.apply(np.sqrt) - tm.assert_series_equal(np.sqrt(self.frame['A']), applied['A']) + applied = float_frame.apply(np.sqrt) + tm.assert_series_equal(np.sqrt(float_frame['A']), applied['A']) # aggregator - applied = self.frame.apply(np.mean) - assert applied['A'] == np.mean(self.frame['A']) + applied = float_frame.apply(np.mean) + assert applied['A'] == np.mean(float_frame['A']) - d = self.frame.index[0] - applied = self.frame.apply(np.mean, axis=1) - assert applied[d] == np.mean(self.frame.xs(d)) - assert applied.index is self.frame.index # want this + d = float_frame.index[0] + applied = float_frame.apply(np.mean, axis=1) + assert applied[d] == np.mean(float_frame.xs(d)) + assert applied.index is float_frame.index # want this # invalid axis df = DataFrame( @@ -65,22 +76,22 @@ def test_apply_mixed_datetimelike(self): result = df.apply(lambda x: x, axis=1) assert_frame_equal(result, df) - def test_apply_empty(self): + def test_apply_empty(self, float_frame, empty_frame): # empty - applied = self.empty.apply(np.sqrt) + applied = empty_frame.apply(np.sqrt) assert applied.empty - applied = self.empty.apply(np.mean) + applied = empty_frame.apply(np.mean) assert applied.empty - no_rows = self.frame[:0] + no_rows = float_frame[:0] result = no_rows.apply(lambda x: x.mean()) - expected = Series(np.nan, index=self.frame.columns) + expected = Series(np.nan, index=float_frame.columns) assert_series_equal(result, expected) - no_cols = self.frame.loc[:, []] + no_cols = float_frame.loc[:, []] result = no_cols.apply(lambda x: x.mean(), axis=1) - expected = Series(np.nan, index=self.frame.index) + expected = Series(np.nan, index=float_frame.index) assert_series_equal(result, expected) # 2476 @@ -88,12 +99,12 @@ def test_apply_empty(self): rs = xp.apply(lambda x: x['a'], axis=1) assert_frame_equal(xp, rs) - def test_apply_with_reduce_empty(self): + def test_apply_with_reduce_empty(self, empty_frame): # reduce with an empty DataFrame x = [] - result = self.empty.apply(x.append, axis=1, result_type='expand') - assert_frame_equal(result, self.empty) - result = self.empty.apply(x.append, axis=1, result_type='reduce') + result = empty_frame.apply(x.append, axis=1, result_type='expand') + assert_frame_equal(result, empty_frame) + result = empty_frame.apply(x.append, axis=1, result_type='reduce') assert_series_equal(result, Series( [], index=pd.Index([], dtype=object))) @@ -107,10 +118,10 @@ def test_apply_with_reduce_empty(self): # Ensure that x.append hasn't been called assert x == [] - def test_apply_deprecate_reduce(self): + def test_apply_deprecate_reduce(self, empty_frame): x = [] with tm.assert_produces_warning(FutureWarning): - self.empty.apply(x.append, axis=1, reduce=True) + empty_frame.apply(x.append, axis=1, reduce=True) def test_apply_standard_nonunique(self): df = DataFrame( @@ -130,110 +141,98 @@ def test_apply_standard_nonunique(self): pytest.param([], {'numeric_only': True}, id='optional_kwds'), pytest.param([1, None], {'numeric_only': True}, id='args_and_kwds') ]) - def test_apply_with_string_funcs(self, func, args, kwds): - result = self.frame.apply(func, *args, **kwds) - expected = getattr(self.frame, func)(*args, **kwds) + def test_apply_with_string_funcs(self, float_frame, func, args, kwds): + result = float_frame.apply(func, *args, **kwds) + expected = getattr(float_frame, func)(*args, **kwds) tm.assert_series_equal(result, expected) - def test_apply_broadcast_deprecated(self): + def test_apply_broadcast_deprecated(self, float_frame): with tm.assert_produces_warning(FutureWarning): - self.frame.apply(np.mean, broadcast=True) + float_frame.apply(np.mean, broadcast=True) - def test_apply_broadcast(self): + def test_apply_broadcast(self, float_frame, int_frame_const_col): # scalars - result = self.frame.apply(np.mean, result_type='broadcast') - expected = DataFrame([self.frame.mean()], index=self.frame.index) + result = float_frame.apply(np.mean, result_type='broadcast') + expected = DataFrame([float_frame.mean()], index=float_frame.index) tm.assert_frame_equal(result, expected) - result = self.frame.apply(np.mean, axis=1, result_type='broadcast') - m = self.frame.mean(axis=1) - expected = DataFrame({c: m for c in self.frame.columns}) + result = float_frame.apply(np.mean, axis=1, result_type='broadcast') + m = float_frame.mean(axis=1) + expected = DataFrame({c: m for c in float_frame.columns}) tm.assert_frame_equal(result, expected) # lists - result = self.frame.apply( - lambda x: list(range(len(self.frame.columns))), + result = float_frame.apply( + lambda x: list(range(len(float_frame.columns))), axis=1, result_type='broadcast') - m = list(range(len(self.frame.columns))) - expected = DataFrame([m] * len(self.frame.index), + m = list(range(len(float_frame.columns))) + expected = DataFrame([m] * len(float_frame.index), dtype='float64', - index=self.frame.index, - columns=self.frame.columns) + index=float_frame.index, + columns=float_frame.columns) tm.assert_frame_equal(result, expected) - result = self.frame.apply(lambda x: list(range(len(self.frame.index))), - result_type='broadcast') - m = list(range(len(self.frame.index))) - expected = DataFrame({c: m for c in self.frame.columns}, + result = float_frame.apply(lambda x: + list(range(len(float_frame.index))), + result_type='broadcast') + m = list(range(len(float_frame.index))) + expected = DataFrame({c: m for c in float_frame.columns}, dtype='float64', - index=self.frame.index) + index=float_frame.index) tm.assert_frame_equal(result, expected) # preserve columns - df = DataFrame(np.tile(np.arange(3), 6).reshape(6, -1) + 1, - columns=list('ABC')) - result = df.apply(lambda x: [1, 2, 3], - axis=1, - result_type='broadcast') + df = int_frame_const_col + result = df.apply(lambda x: [1, 2, 3], axis=1, result_type='broadcast') tm.assert_frame_equal(result, df) - df = DataFrame(np.tile(np.arange(3), 6).reshape(6, -1) + 1, - columns=list('ABC')) + df = int_frame_const_col result = df.apply(lambda x: Series([1, 2, 3], index=list('abc')), - axis=1, - result_type='broadcast') + axis=1, result_type='broadcast') expected = df.copy() tm.assert_frame_equal(result, expected) - def test_apply_broadcast_error(self): - df = DataFrame( - np.tile(np.arange(3, dtype='int64'), 6).reshape(6, -1) + 1, - columns=['A', 'B', 'C']) + def test_apply_broadcast_error(self, int_frame_const_col): + df = int_frame_const_col # > 1 ndim with pytest.raises(ValueError): df.apply(lambda x: np.array([1, 2]).reshape(-1, 2), - axis=1, - result_type='broadcast') + axis=1, result_type='broadcast') # cannot broadcast with pytest.raises(ValueError): - df.apply(lambda x: [1, 2], - axis=1, - result_type='broadcast') + df.apply(lambda x: [1, 2], axis=1, result_type='broadcast') with pytest.raises(ValueError): - df.apply(lambda x: Series([1, 2]), - axis=1, - result_type='broadcast') + df.apply(lambda x: Series([1, 2]), axis=1, result_type='broadcast') - def test_apply_raw(self): - result0 = self.frame.apply(np.mean, raw=True) - result1 = self.frame.apply(np.mean, axis=1, raw=True) + def test_apply_raw(self, float_frame): + result0 = float_frame.apply(np.mean, raw=True) + result1 = float_frame.apply(np.mean, axis=1, raw=True) - expected0 = self.frame.apply(lambda x: x.values.mean()) - expected1 = self.frame.apply(lambda x: x.values.mean(), axis=1) + expected0 = float_frame.apply(lambda x: x.values.mean()) + expected1 = float_frame.apply(lambda x: x.values.mean(), axis=1) assert_series_equal(result0, expected0) assert_series_equal(result1, expected1) # no reduction - result = self.frame.apply(lambda x: x * 2, raw=True) - expected = self.frame * 2 + result = float_frame.apply(lambda x: x * 2, raw=True) + expected = float_frame * 2 assert_frame_equal(result, expected) - def test_apply_axis1(self): - d = self.frame.index[0] - tapplied = self.frame.apply(np.mean, axis=1) - assert tapplied[d] == np.mean(self.frame.xs(d)) + def test_apply_axis1(self, float_frame): + d = float_frame.index[0] + tapplied = float_frame.apply(np.mean, axis=1) + assert tapplied[d] == np.mean(float_frame.xs(d)) - def test_apply_ignore_failures(self): - result = frame_apply(self.mixed_frame, - np.mean, 0, + def test_apply_ignore_failures(self, float_string_frame): + result = frame_apply(float_string_frame, np.mean, 0, ignore_failures=True).apply_standard() - expected = self.mixed_frame._get_numeric_data().apply(np.mean) + expected = float_string_frame._get_numeric_data().apply(np.mean) assert_series_equal(result, expected) def test_apply_mixed_dtype_corner(self): @@ -288,7 +287,7 @@ def _checkit(axis=0, raw=False): result = no_cols.apply(lambda x: x.mean(), result_type='broadcast') assert isinstance(result, DataFrame) - def test_apply_with_args_kwds(self): + def test_apply_with_args_kwds(self, float_frame): def add_some(x, howmuch=0): return x + howmuch @@ -298,26 +297,26 @@ def agg_and_add(x, howmuch=0): def subtract_and_divide(x, sub, divide=1): return (x - sub) / divide - result = self.frame.apply(add_some, howmuch=2) - exp = self.frame.apply(lambda x: x + 2) + result = float_frame.apply(add_some, howmuch=2) + exp = float_frame.apply(lambda x: x + 2) assert_frame_equal(result, exp) - result = self.frame.apply(agg_and_add, howmuch=2) - exp = self.frame.apply(lambda x: x.mean() + 2) + result = float_frame.apply(agg_and_add, howmuch=2) + exp = float_frame.apply(lambda x: x.mean() + 2) assert_series_equal(result, exp) - res = self.frame.apply(subtract_and_divide, args=(2,), divide=2) - exp = self.frame.apply(lambda x: (x - 2.) / 2.) + res = float_frame.apply(subtract_and_divide, args=(2,), divide=2) + exp = float_frame.apply(lambda x: (x - 2.) / 2.) assert_frame_equal(res, exp) - def test_apply_yield_list(self): - result = self.frame.apply(list) - assert_frame_equal(result, self.frame) + def test_apply_yield_list(self, float_frame): + result = float_frame.apply(list) + assert_frame_equal(result, float_frame) - def test_apply_reduce_Series(self): - self.frame.loc[::2, 'A'] = np.nan - expected = self.frame.mean(1) - result = self.frame.apply(np.mean, axis=1) + def test_apply_reduce_Series(self, float_frame): + float_frame.loc[::2, 'A'] = np.nan + expected = float_frame.mean(1) + result = float_frame.apply(np.mean, axis=1) assert_series_equal(result, expected) def test_apply_differently_indexed(self): @@ -408,31 +407,31 @@ def test_apply_convert_objects(self): result = data.apply(lambda x: x, axis=1) assert_frame_equal(result._convert(datetime=True), data) - def test_apply_attach_name(self): - result = self.frame.apply(lambda x: x.name) - expected = Series(self.frame.columns, index=self.frame.columns) + def test_apply_attach_name(self, float_frame): + result = float_frame.apply(lambda x: x.name) + expected = Series(float_frame.columns, index=float_frame.columns) assert_series_equal(result, expected) - result = self.frame.apply(lambda x: x.name, axis=1) - expected = Series(self.frame.index, index=self.frame.index) + result = float_frame.apply(lambda x: x.name, axis=1) + expected = Series(float_frame.index, index=float_frame.index) assert_series_equal(result, expected) # non-reductions - result = self.frame.apply(lambda x: np.repeat(x.name, len(x))) - expected = DataFrame(np.tile(self.frame.columns, - (len(self.frame.index), 1)), - index=self.frame.index, - columns=self.frame.columns) + 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) assert_frame_equal(result, expected) - result = self.frame.apply(lambda x: np.repeat(x.name, len(x)), - axis=1) - expected = Series(np.repeat(t[0], len(self.frame.columns)) - for t in self.frame.itertuples()) - expected.index = self.frame.index + 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 assert_series_equal(result, expected) - def test_apply_multi_index(self): + def test_apply_multi_index(self, float_frame): index = MultiIndex.from_arrays([['a', 'a', 'b'], ['c', 'd', 'd']]) s = DataFrame([[1, 2], [3, 4], [5, 6]], index=index, @@ -463,13 +462,13 @@ def test_apply_dict(self): assert_frame_equal(reduce_false, df) assert_series_equal(reduce_none, dicts) - def test_applymap(self): - applied = self.frame.applymap(lambda x: x * 2) - tm.assert_frame_equal(applied, self.frame * 2) - self.frame.applymap(type) + def test_applymap(self, float_frame): + applied = float_frame.applymap(lambda x: x * 2) + tm.assert_frame_equal(applied, float_frame * 2) + float_frame.applymap(type) # gh-465: function returning tuples - result = self.frame.applymap(lambda x: (x, x)) + result = float_frame.applymap(lambda x: (x, x)) assert isinstance(result['A'][0], tuple) # gh-2909: object conversion to float in constructor? @@ -721,33 +720,27 @@ def test_consistent_coerce_for_shapes(self): expected = Series([[1, 2] for t in df.itertuples()]) assert_series_equal(result, expected) - def test_consistent_names(self): + def test_consistent_names(self, int_frame_const_col): # if a Series is returned, we should use the resulting index names - df = DataFrame( - np.tile(np.arange(3, dtype='int64'), 6).reshape(6, -1) + 1, - columns=['A', 'B', 'C']) + df = int_frame_const_col result = df.apply(lambda x: Series([1, 2, 3], index=['test', 'other', 'cols']), axis=1) - expected = DataFrame( - np.tile(np.arange(3, dtype='int64'), 6).reshape(6, -1) + 1, - columns=['test', 'other', 'cols']) + expected = int_frame_const_col.rename(columns={'A': 'test', + 'B': 'other', + 'C': 'cols'}) assert_frame_equal(result, expected) - result = df.apply( - lambda x: pd.Series([1, 2], index=['test', 'other']), axis=1) - expected = DataFrame( - np.tile(np.arange(2, dtype='int64'), 6).reshape(6, -1) + 1, - columns=['test', 'other']) + result = df.apply(lambda x: Series([1, 2], index=['test', 'other']), + axis=1) + expected = expected[['test', 'other']] assert_frame_equal(result, expected) - def test_result_type(self): + def test_result_type(self, int_frame_const_col): # result_type should be consistent no matter which # path we take in the code - df = DataFrame( - np.tile(np.arange(3, dtype='int64'), 6).reshape(6, -1) + 1, - columns=['A', 'B', 'C']) + df = int_frame_const_col result = df.apply(lambda x: [1, 2, 3], axis=1, result_type='expand') expected = df.copy() @@ -765,11 +758,8 @@ def test_result_type(self): assert_frame_equal(result, expected) columns = ['other', 'col', 'names'] - result = df.apply( - lambda x: pd.Series([1, 2, 3], - index=columns), - axis=1, - result_type='broadcast') + result = df.apply(lambda x: Series([1, 2, 3], index=columns), + axis=1, result_type='broadcast') expected = df.copy() assert_frame_equal(result, expected) @@ -780,24 +770,18 @@ def test_result_type(self): # series result with other index columns = ['other', 'col', 'names'] - result = df.apply( - lambda x: pd.Series([1, 2, 3], index=columns), - axis=1) + result = df.apply(lambda x: Series([1, 2, 3], index=columns), axis=1) expected = df.copy() expected.columns = columns assert_frame_equal(result, expected) @pytest.mark.parametrize("result_type", ['foo', 1]) - def test_result_type_error(self, result_type): + def test_result_type_error(self, result_type, int_frame_const_col): # allowed result_type - df = DataFrame( - np.tile(np.arange(3, dtype='int64'), 6).reshape(6, -1) + 1, - columns=['A', 'B', 'C']) + df = int_frame_const_col with pytest.raises(ValueError): - df.apply(lambda x: [1, 2, 3], - axis=1, - result_type=result_type) + df.apply(lambda x: [1, 2, 3], axis=1, result_type=result_type) @pytest.mark.parametrize( "box", @@ -805,19 +789,17 @@ def test_result_type_error(self, result_type): lambda x: tuple(x), lambda x: np.array(x, dtype='int64')], ids=['list', 'tuple', 'array']) - def test_consistency_for_boxed(self, box): + def test_consistency_for_boxed(self, box, int_frame_const_col): # passing an array or list should not affect the output shape - df = DataFrame( - np.tile(np.arange(3, dtype='int64'), 6).reshape(6, -1) + 1, - columns=['A', 'B', 'C']) + 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()]) assert_series_equal(result, expected) result = df.apply(lambda x: box([1, 2]), axis=1, result_type='expand') - expected = DataFrame( - np.tile(np.arange(2, dtype='int64'), 6).reshape(6, -1) + 1) + expected = int_frame_const_col[['A', 'B']].rename(columns={'A': 0, + 'B': 1}) assert_frame_equal(result, expected) @@ -840,71 +822,71 @@ def zip_frames(frames, axis=1): return pd.DataFrame(zipped) -class TestDataFrameAggregate(TestData): +class TestDataFrameAggregate(): - def test_agg_transform(self, axis): + def test_agg_transform(self, axis, float_frame): other_axis = 1 if axis in {0, 'index'} else 0 with np.errstate(all='ignore'): - f_abs = np.abs(self.frame) - f_sqrt = np.sqrt(self.frame) + f_abs = np.abs(float_frame) + f_sqrt = np.sqrt(float_frame) # ufunc - result = self.frame.transform(np.sqrt, axis=axis) + result = float_frame.transform(np.sqrt, axis=axis) expected = f_sqrt.copy() assert_frame_equal(result, expected) - result = self.frame.apply(np.sqrt, axis=axis) + result = float_frame.apply(np.sqrt, axis=axis) assert_frame_equal(result, expected) - result = self.frame.transform(np.sqrt, axis=axis) + result = float_frame.transform(np.sqrt, axis=axis) assert_frame_equal(result, expected) # list-like - result = self.frame.apply([np.sqrt], axis=axis) + result = float_frame.apply([np.sqrt], axis=axis) expected = f_sqrt.copy() if axis in {0, 'index'}: expected.columns = pd.MultiIndex.from_product( - [self.frame.columns, ['sqrt']]) + [float_frame.columns, ['sqrt']]) else: expected.index = pd.MultiIndex.from_product( - [self.frame.index, ['sqrt']]) + [float_frame.index, ['sqrt']]) assert_frame_equal(result, expected) - result = self.frame.transform([np.sqrt], axis=axis) + result = float_frame.transform([np.sqrt], axis=axis) 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 = self.frame.apply([np.abs, np.sqrt], axis=axis) + 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 = pd.MultiIndex.from_product( - [self.frame.columns, ['absolute', 'sqrt']]) + [float_frame.columns, ['absolute', 'sqrt']]) else: expected.index = pd.MultiIndex.from_product( - [self.frame.index, ['absolute', 'sqrt']]) + [float_frame.index, ['absolute', 'sqrt']]) assert_frame_equal(result, expected) - result = self.frame.transform([np.abs, 'sqrt'], axis=axis) + result = float_frame.transform([np.abs, 'sqrt'], axis=axis) assert_frame_equal(result, expected) - def test_transform_and_agg_err(self, axis): + def test_transform_and_agg_err(self, axis, float_frame): # cannot both transform and agg def f(): - self.frame.transform(['max', 'min'], axis=axis) + float_frame.transform(['max', 'min'], axis=axis) pytest.raises(ValueError, f) def f(): with np.errstate(all='ignore'): - self.frame.agg(['max', 'sqrt'], axis=axis) + float_frame.agg(['max', 'sqrt'], axis=axis) pytest.raises(ValueError, f) def f(): with np.errstate(all='ignore'): - self.frame.transform(['max', 'sqrt'], axis=axis) + float_frame.transform(['max', 'sqrt'], axis=axis) pytest.raises(ValueError, f) df = pd.DataFrame({'A': range(5), 'B': 5}) @@ -974,49 +956,49 @@ def test_agg_dict_nested_renaming_depr(self): df.agg({'A': {'foo': 'min'}, 'B': {'bar': 'max'}}) - def test_agg_reduce(self, axis): + def test_agg_reduce(self, axis, float_frame): other_axis = 1 if axis in {0, 'index'} else 0 - name1, name2 = self.frame.axes[other_axis].unique()[:2].sort_values() + name1, name2 = float_frame.axes[other_axis].unique()[:2].sort_values() # all reducers - expected = pd.concat([self.frame.mean(axis=axis), - self.frame.max(axis=axis), - self.frame.sum(axis=axis), + 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 = self.frame.agg(['mean', 'max', 'sum'], axis=axis) + result = float_frame.agg(['mean', 'max', 'sum'], axis=axis) assert_frame_equal(result, expected) # dict input with scalars func = OrderedDict([(name1, 'mean'), (name2, 'sum')]) - result = self.frame.agg(func, axis=axis) - expected = Series([self.frame.loc(other_axis)[name1].mean(), - self.frame.loc(other_axis)[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]) assert_series_equal(result, expected) # dict input with lists func = OrderedDict([(name1, ['mean']), (name2, ['sum'])]) - result = self.frame.agg(func, axis=axis) + result = float_frame.agg(func, axis=axis) expected = DataFrame({ - name1: Series([self.frame.loc(other_axis)[name1].mean()], + name1: Series([float_frame.loc(other_axis)[name1].mean()], index=['mean']), - name2: Series([self.frame.loc(other_axis)[name2].sum()], + name2: Series([float_frame.loc(other_axis)[name2].sum()], index=['sum'])}) expected = expected.T if axis in {1, 'columns'} else expected assert_frame_equal(result, expected) # dict input with lists with multiple func = OrderedDict([(name1, ['mean', 'sum']), (name2, ['sum', 'max'])]) - result = self.frame.agg(func, axis=axis) + result = float_frame.agg(func, axis=axis) expected = DataFrame(OrderedDict([ - (name1, Series([self.frame.loc(other_axis)[name1].mean(), - self.frame.loc(other_axis)[name1].sum()], + (name1, Series([float_frame.loc(other_axis)[name1].mean(), + float_frame.loc(other_axis)[name1].sum()], index=['mean', 'sum'])), - (name2, Series([self.frame.loc(other_axis)[name2].sum(), - self.frame.loc(other_axis)[name2].max()], + (name2, Series([float_frame.loc(other_axis)[name2].sum(), + float_frame.loc(other_axis)[name2].max()], index=['sum', 'max'])), ])) expected = expected.T if axis in {1, 'columns'} else expected From 2ab57b2dfc7005e580698c3e0bdf9478e96647db Mon Sep 17 00:00:00 2001 From: Scott McAllister Date: Sun, 23 Sep 2018 16:11:02 -0400 Subject: [PATCH 20/85] BUG:reorder type check/conversion so wide_to_long handles str arg for stubnames. GH22468 (#22490) --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/core/reshape/melt.py | 6 +++--- pandas/tests/reshape/test_melt.py | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 618d7454c67fe5..9d559acfa59e7b 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -802,6 +802,7 @@ Reshaping - Bug in :meth:`DataFrame.replace` raises ``RecursionError`` when replacing empty lists (:issue:`22083`) - Bug in :meth:`Series.replace` and meth:`DataFrame.replace` when dict is used as the ``to_replace`` value and one key in the dict is is another key's value, the results were inconsistent between using integer key and using string key (:issue:`20656`) - Bug in :meth:`DataFrame.drop_duplicates` for empty ``DataFrame`` which incorrectly raises an error (:issue:`20516`) +- Bug in :func:`pandas.wide_to_long` when a string is passed to the stubnames argument and a column name is a substring of that stubname (:issue:`22468`) Build Changes ^^^^^^^^^^^^^ diff --git a/pandas/core/reshape/melt.py b/pandas/core/reshape/melt.py index f4b96c8f1ca49c..26221143c0cdfd 100644 --- a/pandas/core/reshape/melt.py +++ b/pandas/core/reshape/melt.py @@ -409,14 +409,14 @@ def melt_stub(df, stub, i, j, value_vars, sep): return newdf.set_index(i + [j]) - if any(col in stubnames for col in df.columns): - raise ValueError("stubname can't be identical to a column name") - if not is_list_like(stubnames): stubnames = [stubnames] else: stubnames = list(stubnames) + if any(col in stubnames for col in df.columns): + raise ValueError("stubname can't be identical to a column name") + if not is_list_like(i): i = [i] else: diff --git a/pandas/tests/reshape/test_melt.py b/pandas/tests/reshape/test_melt.py index 81570de7586de8..e83a2cb483de70 100644 --- a/pandas/tests/reshape/test_melt.py +++ b/pandas/tests/reshape/test_melt.py @@ -640,3 +640,24 @@ def test_float_suffix(self): 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 = pd.DataFrame.from_dict(wide_data) + expected = pd.wide_to_long(wide_df, + stubnames=['PA'], + i=['node_id', 'A'], + j='time') + result = pd.wide_to_long(wide_df, + stubnames='PA', + i=['node_id', 'A'], + j='time') + tm.assert_frame_equal(result, expected) From 64b88e86677349de66071dffd74b5c3254283e2a Mon Sep 17 00:00:00 2001 From: Troels Nielsen Date: Mon, 24 Sep 2018 05:53:09 +0200 Subject: [PATCH 21/85] BUG: read_table and read_csv crash (#22750) A missing null-pointer check made read_table and read_csv prone to crash on badly encoded text. Add null-pointer check. Closes gh-22748. --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/_libs/src/parser/io.c | 6 +++++- pandas/tests/io/parser/common.py | 9 +++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 9d559acfa59e7b..6c91b6374b8af1 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -756,6 +756,7 @@ I/O - :func:`read_html()` no longer ignores all-whitespace ```` within ```` when considering the ``skiprows`` and ``header`` arguments. Previously, users had to decrease their ``header`` and ``skiprows`` values on such tables to work around the issue. (:issue:`21641`) - :func:`read_excel()` will correctly show the deprecation warning for previously deprecated ``sheetname`` (:issue:`17994`) +- :func:`read_csv()` and func:`read_table()` will throw ``UnicodeError`` and not coredump on badly encoded strings (:issue:`22748`) - :func:`read_csv()` will correctly parse timezone-aware datetimes (:issue:`22256`) - :func:`read_sas()` will parse numbers in sas7bdat-files that have width less than 8 bytes correctly. (:issue:`21616`) - :func:`read_sas()` will correctly parse sas7bdat files with many columns (:issue:`22628`) diff --git a/pandas/_libs/src/parser/io.c b/pandas/_libs/src/parser/io.c index 8300e889d4157c..19271c78501ba4 100644 --- a/pandas/_libs/src/parser/io.c +++ b/pandas/_libs/src/parser/io.c @@ -150,7 +150,11 @@ void *buffer_rd_bytes(void *source, size_t nbytes, size_t *bytes_read, return NULL; } else if (!PyBytes_Check(result)) { tmp = PyUnicode_AsUTF8String(result); - Py_XDECREF(result); + Py_DECREF(result); + if (tmp == NULL) { + PyGILState_Release(state); + return NULL; + } result = tmp; } diff --git a/pandas/tests/io/parser/common.py b/pandas/tests/io/parser/common.py index 9e871d27f0ce8a..064385e60c4ecc 100644 --- a/pandas/tests/io/parser/common.py +++ b/pandas/tests/io/parser/common.py @@ -9,6 +9,7 @@ import sys from datetime import datetime from collections import OrderedDict +from io import TextIOWrapper import pytest import numpy as np @@ -1609,3 +1610,11 @@ def test_skip_bad_lines(self): val = sys.stderr.getvalue() assert 'Skipping line 3' in val assert 'Skipping line 5' in val + + def test_buffer_rd_bytes_bad_unicode(self): + # Regression test for #22748 + t = BytesIO(b"\xB0") + if PY3: + t = TextIOWrapper(t, encoding='ascii', errors='surrogateescape') + with pytest.raises(UnicodeError): + pd.read_csv(t, encoding='UTF-8') From 3ab9dbd64b4a057eda53b841f43999fb319869e9 Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Mon, 24 Sep 2018 14:32:07 +0200 Subject: [PATCH 22/85] DOC: fixup spacing in to_csv docstring (GH22475) (#22816) --- pandas/core/generic.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 9d19b02c4d1fb8..19ac4b49358d4f 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -9512,6 +9512,7 @@ def to_csv(self, path_or_buf=None, sep=",", na_rep='', float_format=None, path_or_buf : str or file handle, default None File path or object, if None is provided the result is returned as a string. + .. versionchanged:: 0.24.0 Was previously named "path" for Series. sep : str, default ',' @@ -9525,6 +9526,7 @@ def to_csv(self, path_or_buf=None, sep=",", na_rep='', float_format=None, header : bool or list of str, default True Write out the column names. If a list of strings is given it is assumed to be aliases for the column names. + .. versionchanged:: 0.24.0 Previously defaulted to False for Series. index : bool, default True @@ -9546,6 +9548,7 @@ def to_csv(self, path_or_buf=None, sep=",", na_rep='', float_format=None, is path-like, then detect compression from the following extensions: '.gz', '.bz2', '.zip' or '.xz'. (otherwise no compression). + .. versionchanged:: 0.24.0 'infer' option added and set to default. quoting : optional constant from csv module @@ -9563,6 +9566,7 @@ def to_csv(self, path_or_buf=None, sep=",", na_rep='', float_format=None, Write MultiIndex columns as a list of tuples (if True) or in the new, expanded format, where each MultiIndex column is a row in the CSV (if False). + .. deprecated:: 0.21.0 This argument will be removed and will always write each row of the multi-index as a separate row in the CSV file. @@ -9586,7 +9590,7 @@ def to_csv(self, path_or_buf=None, sep=",", na_rep='', float_format=None, See Also -------- pandas.read_csv : Load a CSV file into a DataFrame. - pandas.to_excel: Load an Excel file into a DataFrame. + pandas.to_excel : Load an Excel file into a DataFrame. Examples -------- From 183a41620c596bfc58edef5deb53d53e8bb4b798 Mon Sep 17 00:00:00 2001 From: Anjali2019 Date: Tue, 25 Sep 2018 14:30:33 +0200 Subject: [PATCH 23/85] TST: Fixturize series/test_validate.py (#22756) --- pandas/tests/series/test_validate.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/pandas/tests/series/test_validate.py b/pandas/tests/series/test_validate.py index a0cde5f81d021b..8c4b6ee5b1d75c 100644 --- a/pandas/tests/series/test_validate.py +++ b/pandas/tests/series/test_validate.py @@ -1,14 +1,7 @@ -from pandas.core.series import Series - import pytest import pandas.util.testing as tm -@pytest.fixture -def series(): - return Series([1, 2, 3, 4, 5]) - - class TestSeriesValidate(object): """Tests for error handling related to data types of method arguments.""" @@ -16,7 +9,7 @@ class TestSeriesValidate(object): "sort_values", "sort_index", "rename", "dropna"]) @pytest.mark.parametrize("inplace", [1, "True", [1, 2, 3], 5.0]) - def test_validate_bool_args(self, series, func, inplace): + def test_validate_bool_args(self, string_series, func, inplace): msg = "For argument \"inplace\" expected type bool" kwargs = dict(inplace=inplace) @@ -24,4 +17,4 @@ def test_validate_bool_args(self, series, func, inplace): kwargs["name"] = "hello" with tm.assert_raises_regex(ValueError, msg): - getattr(series, func)(**kwargs) + getattr(string_series, func)(**kwargs) From 2b5477e107ae1ff4090c43c269a0389dfac3560b Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Tue, 25 Sep 2018 08:46:47 -0400 Subject: [PATCH 24/85] Add Azure Pipelines badge to readme (#22828) --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 3dde5e5e2a76e8..bf90f76ae7bd15 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,14 @@ + + + + + Azure Pipelines build status + + + Coverage   From ea83ccc77e8b2e206168b41dfd93dcc68279b7ef Mon Sep 17 00:00:00 2001 From: Jeff Reback Date: Tue, 25 Sep 2018 08:48:40 -0400 Subject: [PATCH 25/85] DOC: remove appveyor badge from readme (#22829) --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index bf90f76ae7bd15..f26b9598bb5d37 100644 --- a/README.md +++ b/README.md @@ -53,14 +53,6 @@ - - - - - appveyor build status - - - From 30b942a7b1a8628c0ce9a4931f83e3b31cd4965e Mon Sep 17 00:00:00 2001 From: Gosuke Shibahara Date: Tue, 25 Sep 2018 05:55:04 -0700 Subject: [PATCH 26/85] Fix DataFrame.to_string() justification (2) (#22505) --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/io/formats/format.py | 5 +--- pandas/tests/io/formats/test_format.py | 32 ++++++++++++++++++++++---- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 6c91b6374b8af1..c067adc8936a29 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -762,6 +762,7 @@ I/O - :func:`read_sas()` will correctly parse sas7bdat files with many columns (:issue:`22628`) - :func:`read_sas()` will correctly parse sas7bdat files with data page types having also bit 7 set (so page type is 128 + 256 = 384) (:issue:`16615`) - Bug in :meth:`detect_client_encoding` where potential ``IOError`` goes unhandled when importing in a mod_wsgi process due to restricted access to stdout. (:issue:`21552`) +- Bug in :func:`to_string()` that broke column alignment when ``index=False`` and width of first column's values is greater than the width of first column's header (:issue:`16839`, :issue:`13032`) Plotting ^^^^^^^^ diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py index 1ff06138768380..db86409adc2b00 100644 --- a/pandas/io/formats/format.py +++ b/pandas/io/formats/format.py @@ -288,8 +288,7 @@ def to_string(self): if self.index: result = self.adj.adjoin(3, *[fmt_index[1:], fmt_values]) else: - result = self.adj.adjoin(3, fmt_values).replace('\n ', - '\n').strip() + result = self.adj.adjoin(3, fmt_values) if self.header and have_header: result = fmt_index[0] + '\n' + result @@ -650,8 +649,6 @@ def to_string(self): self._chk_truncate() strcols = self._to_str_columns() text = self.adj.adjoin(1, *strcols) - if not self.index: - text = text.replace('\n ', '\n').strip() self.buf.writelines(text) if self.should_show_dimensions: diff --git a/pandas/tests/io/formats/test_format.py b/pandas/tests/io/formats/test_format.py index ffbc978b92ba50..03e830fb09ad6b 100644 --- a/pandas/tests/io/formats/test_format.py +++ b/pandas/tests/io/formats/test_format.py @@ -1269,18 +1269,42 @@ def test_to_string_specified_header(self): df.to_string(header=['X']) def test_to_string_no_index(self): - df = DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]}) + # GH 16839, GH 13032 + df = DataFrame({'x': [11, 22], 'y': [33, -44], 'z': ['AAA', ' ']}) df_s = df.to_string(index=False) - expected = "x y\n1 4\n2 5\n3 6" + # Leading space is expected for positive numbers. + expected = (" x y z\n" + " 11 33 AAA\n" + " 22 -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_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 \\\n1 \n2 \n3 \n\ny \n4 \n5 \n6" + 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 \\\n 11 \n 22 \n 33 \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 @@ -1793,7 +1817,7 @@ def test_to_string_without_index(self): # GH 11729 Test index=False option s = Series([1, 2, 3, 4]) result = s.to_string(index=False) - expected = (u('1\n') + '2\n' + '3\n' + '4') + expected = (u(' 1\n') + ' 2\n' + ' 3\n' + ' 4') assert result == expected def test_unicode_name_in_footer(self): From 1c4130d7834be9b762b41f9a9beaa1f34d9d272f Mon Sep 17 00:00:00 2001 From: Troels Nielsen Date: Tue, 25 Sep 2018 14:58:20 +0200 Subject: [PATCH 27/85] BUG: nlargest/nsmallest gave wrong result (#22752) (#22754) --- asv_bench/benchmarks/frame_methods.py | 13 ++++-- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/core/algorithms.py | 57 +++++++++++++++++---------- pandas/tests/frame/test_analytics.py | 18 +++++++++ 4 files changed, 65 insertions(+), 24 deletions(-) diff --git a/asv_bench/benchmarks/frame_methods.py b/asv_bench/benchmarks/frame_methods.py index 1819cfa2725dbf..f911d506b1f4f0 100644 --- a/asv_bench/benchmarks/frame_methods.py +++ b/asv_bench/benchmarks/frame_methods.py @@ -505,14 +505,21 @@ class NSort(object): param_names = ['keep'] def setup(self, keep): - self.df = DataFrame(np.random.randn(1000, 3), columns=list('ABC')) + self.df = DataFrame(np.random.randn(100000, 3), + columns=list('ABC')) - def time_nlargest(self, keep): + def time_nlargest_one_column(self, keep): self.df.nlargest(100, 'A', keep=keep) - def time_nsmallest(self, keep): + def time_nlargest_two_columns(self, keep): + self.df.nlargest(100, ['A', 'B'], keep=keep) + + def time_nsmallest_one_column(self, keep): self.df.nsmallest(100, 'A', keep=keep) + def time_nsmallest_two_columns(self, keep): + self.df.nsmallest(100, ['A', 'B'], keep=keep) + class Describe(object): diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index c067adc8936a29..3b61fde77cb9f0 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -819,6 +819,7 @@ Other - :meth:`~pandas.io.formats.style.Styler.background_gradient` now takes a ``text_color_threshold`` parameter to automatically lighten the text color based on the luminance of the background color. This improves readability with dark background colors without the need to limit the background colormap range. (:issue:`21258`) - Require at least 0.28.2 version of ``cython`` to support read-only memoryviews (:issue:`21688`) - :meth:`~pandas.io.formats.style.Styler.background_gradient` now also supports tablewise application (in addition to rowwise and columnwise) with ``axis=None`` (:issue:`15204`) +- :meth:`DataFrame.nlargest` and :meth:`DataFrame.nsmallest` now returns the correct n values when keep != 'all' also when tied on the first columns (:issue:`22752`) - :meth:`~pandas.io.formats.style.Styler.bar` now also supports tablewise application (in addition to rowwise and columnwise) with ``axis=None`` and setting clipping range with ``vmin`` and ``vmax`` (:issue:`21548` and :issue:`21526`). ``NaN`` values are also handled properly. - Logical operations ``&, |, ^`` between :class:`Series` and :class:`Index` will no longer raise ``ValueError`` (:issue:`22092`) - diff --git a/pandas/core/algorithms.py b/pandas/core/algorithms.py index d39e9e08e29470..e91cc8ec1e996b 100644 --- a/pandas/core/algorithms.py +++ b/pandas/core/algorithms.py @@ -1214,41 +1214,56 @@ def get_indexer(current_indexer, other_indexer): indexer = Int64Index([]) for i, column in enumerate(columns): - # For each column we apply method to cur_frame[column]. - # If it is the last column in columns, or if the values - # returned are unique in frame[column] we save this index - # and break - # Otherwise we must save the index of the non duplicated values - # and set the next cur_frame to cur_frame filtered on all - # duplcicated values (#GH15297) + # If it's the last column or if we have the number of + # results desired we are done. + # Otherwise there are duplicates of the largest/smallest + # value and we need to look at the rest of the columns + # to determine which of the rows with the largest/smallest + # value in the column to keep. series = cur_frame[column] - values = getattr(series, method)(cur_n, keep=self.keep) is_last_column = len(columns) - 1 == i - if is_last_column or values.nunique() == series.isin(values).sum(): + values = getattr(series, method)( + cur_n, + keep=self.keep if is_last_column else 'all') - # Last column in columns or values are unique in - # series => values - # is all that matters + if is_last_column or len(values) <= cur_n: indexer = get_indexer(indexer, values.index) break - duplicated_filter = series.duplicated(keep=False) - duplicated = values[duplicated_filter] - non_duplicated = values[~duplicated_filter] - indexer = get_indexer(indexer, non_duplicated.index) + # Now find all values which are equal to + # the (nsmallest: largest)/(nlarrgest: smallest) + # from our series. + border_value = values == values[values.index[-1]] + + # Some of these values are among the top-n + # some aren't. + unsafe_values = values[border_value] + + # These values are definitely among the top-n + safe_values = values[~border_value] + indexer = get_indexer(indexer, safe_values.index) - # Must set cur frame to include all duplicated values - # to consider for the next column, we also can reduce - # cur_n by the current length of the indexer - cur_frame = cur_frame[series.isin(duplicated)] + # Go on and separate the unsafe_values on the remaining + # columns. + cur_frame = cur_frame.loc[unsafe_values.index] cur_n = n - len(indexer) frame = frame.take(indexer) # Restore the index on frame frame.index = original_index.take(indexer) - return frame + + # If there is only one column, the frame is already sorted. + if len(columns) == 1: + return frame + + ascending = method == 'nsmallest' + + return frame.sort_values( + columns, + ascending=ascending, + kind='mergesort') # ------- ## ---- # diff --git a/pandas/tests/frame/test_analytics.py b/pandas/tests/frame/test_analytics.py index 52a52a1fd8752f..baebf414969bea 100644 --- a/pandas/tests/frame/test_analytics.py +++ b/pandas/tests/frame/test_analytics.py @@ -2095,6 +2095,24 @@ def test_n_all_dtypes(self, df_main_dtypes): df.nsmallest(2, list(set(df) - {'category_string', 'string'})) df.nlargest(2, list(set(df) - {'category_string', 'string'})) + @pytest.mark.parametrize('method,expected', [ + ('nlargest', + pd.DataFrame({'a': [2, 2, 2, 1], 'b': [3, 2, 1, 3]}, + index=[2, 1, 0, 3])), + ('nsmallest', + pd.DataFrame({'a': [1, 1, 1, 2], 'b': [1, 2, 3, 1]}, + index=[5, 4, 3, 0]))]) + def test_duplicates_on_starter_columns(self, method, expected): + # regression test for #22752 + + df = pd.DataFrame({ + 'a': [2, 2, 2, 1, 1, 1], + 'b': [1, 2, 3, 3, 2, 1] + }) + + result = getattr(df, method)(4, columns=['a', 'b']) + tm.assert_frame_equal(result, expected) + def test_n_identical_values(self): # GH15297 df = pd.DataFrame({'a': [1] * 5, 'b': [1, 2, 3, 4, 5]}) From 302d43a840797175eee04959b8918d3870722a0e Mon Sep 17 00:00:00 2001 From: Thierry Moisan Date: Tue, 25 Sep 2018 09:07:51 -0400 Subject: [PATCH 28/85] DOC: fix DataFrame.isin docstring and doctests (#22767) --- ci/doctests.sh | 2 +- pandas/core/frame.py | 78 ++++++++++++++++++++++++++------------------ 2 files changed, 47 insertions(+), 33 deletions(-) diff --git a/ci/doctests.sh b/ci/doctests.sh index 48774a1e4d00de..b3d7f6785815aa 100755 --- a/ci/doctests.sh +++ b/ci/doctests.sh @@ -21,7 +21,7 @@ if [ "$DOCTEST" ]; then # DataFrame / Series docstrings pytest --doctest-modules -v pandas/core/frame.py \ - -k"-axes -combine -isin -itertuples -join -nlargest -nsmallest -nunique -pivot_table -quantile -query -reindex -reindex_axis -replace -round -set_index -stack -to_dict -to_stata" + -k"-axes -combine -itertuples -join -nlargest -nsmallest -nunique -pivot_table -quantile -query -reindex -reindex_axis -replace -round -set_index -stack -to_dict -to_stata" if [ $? -ne "0" ]; then RET=1 diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 81d5c112885ec8..721c31c57bc06c 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -7451,52 +7451,66 @@ def to_period(self, freq=None, axis=0, copy=True): def isin(self, values): """ - Return boolean DataFrame showing whether each element in the - DataFrame is contained in values. + Whether each element in the DataFrame is contained in values. Parameters ---------- - values : iterable, Series, DataFrame or dictionary + values : iterable, Series, DataFrame or dict The result will only be true at a location if all the labels match. If `values` is a Series, that's the index. If - `values` is a dictionary, the keys must be the column names, + `values` is a dict, the keys must be the column names, which must match. If `values` is a DataFrame, then both the index and column labels must match. Returns ------- + DataFrame + DataFrame of booleans showing whether each element in the DataFrame + is contained in values. - DataFrame of booleans + See Also + -------- + DataFrame.eq: Equality test for DataFrame. + Series.isin: Equivalent method on Series. + Series.str.contains: Test if pattern or regex is contained within a + string of a Series or Index. Examples -------- - When ``values`` is a list: - - >>> df = pd.DataFrame({'A': [1, 2, 3], 'B': ['a', 'b', 'f']}) - >>> df.isin([1, 3, 12, 'a']) - A B - 0 True True - 1 False False - 2 True False - - When ``values`` is a dict: - - >>> df = pd.DataFrame({'A': [1, 2, 3], 'B': [1, 4, 7]}) - >>> df.isin({'A': [1, 3], 'B': [4, 7, 12]}) - A B - 0 True False # Note that B didn't match the 1 here. - 1 False True - 2 True True - - When ``values`` is a Series or DataFrame: - - >>> df = pd.DataFrame({'A': [1, 2, 3], 'B': ['a', 'b', 'f']}) - >>> df2 = pd.DataFrame({'A': [1, 3, 3, 2], 'B': ['e', 'f', 'f', 'e']}) - >>> df.isin(df2) - A B - 0 True False - 1 False False # Column A in `df2` has a 3, but not at index 1. - 2 True True + + >>> df = pd.DataFrame({'num_legs': [2, 4], 'num_wings': [2, 0]}, + ... index=['falcon', 'dog']) + >>> df + num_legs num_wings + falcon 2 2 + dog 4 0 + + When ``values`` is a list check whether every value in the DataFrame + is present in the list (which animals have 0 or 2 legs or wings) + + >>> df.isin([0, 2]) + num_legs num_wings + falcon True True + dog False True + + When ``values`` is a dict, we can pass values to check for each + column separately: + + >>> df.isin({'num_wings': [0, 3]}) + num_legs num_wings + falcon False False + dog False True + + When ``values`` is a Series or DataFrame the index and column must + match. Note that 'falcon' does not match based on the number of legs + in df2. + + >>> other = pd.DataFrame({'num_legs': [8, 2],'num_wings': [0, 2]}, + ... index=['spider', 'falcon']) + >>> df.isin(other) + num_legs num_wings + falcon True True + dog False False """ if isinstance(values, dict): from pandas.core.reshape.concat import concat From 6b1f46052f0bd69f1a45989f1bfe650ba1c55ee1 Mon Sep 17 00:00:00 2001 From: gfyoung Date: Tue, 25 Sep 2018 09:38:53 -0700 Subject: [PATCH 29/85] ERR: Clarify location of EOF on unbalanced quotes (#22814) Closes gh-22789. --- pandas/_libs/src/parser/tokenizer.c | 2 +- pandas/io/parsers.py | 3 --- pandas/tests/io/parser/common.py | 14 -------------- pandas/tests/io/parser/quoting.py | 17 +++++++++++++++++ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/pandas/_libs/src/parser/tokenizer.c b/pandas/_libs/src/parser/tokenizer.c index da0a9f7498aa83..2fce241027d56f 100644 --- a/pandas/_libs/src/parser/tokenizer.c +++ b/pandas/_libs/src/parser/tokenizer.c @@ -1150,7 +1150,7 @@ static int parser_handle_eof(parser_t *self) { case IN_QUOTED_FIELD: self->error_msg = (char *)malloc(bufsize); snprintf(self->error_msg, bufsize, - "EOF inside string starting at line %lld", + "EOF inside string starting at row %lld", (long long)self->file_lines); return -1; diff --git a/pandas/io/parsers.py b/pandas/io/parsers.py index 8d37bf4c84d5d7..a4f1155117b123 100755 --- a/pandas/io/parsers.py +++ b/pandas/io/parsers.py @@ -2727,9 +2727,6 @@ def _next_iter_line(self, row_num): 'cannot be processed in Python\'s ' 'native csv library at the moment, ' 'so please pass in engine=\'c\' instead') - elif 'newline inside string' in msg: - msg = ('EOF inside string starting with ' - 'line ' + str(row_num)) if self.skipfooter > 0: reason = ('Error could possibly be due to ' diff --git a/pandas/tests/io/parser/common.py b/pandas/tests/io/parser/common.py index 064385e60c4ecc..49e42786d6fb80 100644 --- a/pandas/tests/io/parser/common.py +++ b/pandas/tests/io/parser/common.py @@ -198,20 +198,6 @@ def test_malformed(self): header=1, comment='#', skipfooter=1) - def test_quoting(self): - bad_line_small = """printer\tresult\tvariant_name -Klosterdruckerei\tKlosterdruckerei (1611-1804)\tMuller, Jacob -Klosterdruckerei\tKlosterdruckerei (1611-1804)\tMuller, Jakob -Klosterdruckerei\tKlosterdruckerei (1609-1805)\t"Furststiftische Hofdruckerei, (1609-1805)\tGaller, Alois -Klosterdruckerei\tKlosterdruckerei (1609-1805)\tHochfurstliche Buchhandlung """ # noqa - pytest.raises(Exception, self.read_table, StringIO(bad_line_small), - sep='\t') - - good_line_small = bad_line_small + '"' - df = self.read_table(StringIO(good_line_small), sep='\t') - assert len(df) == 3 - def test_unnamed_columns(self): data = """A,B,C,, 1,2,3,4,5 diff --git a/pandas/tests/io/parser/quoting.py b/pandas/tests/io/parser/quoting.py index 15427aaf9825c4..013e635f80d211 100644 --- a/pandas/tests/io/parser/quoting.py +++ b/pandas/tests/io/parser/quoting.py @@ -9,6 +9,7 @@ import pandas.util.testing as tm from pandas import DataFrame +from pandas.errors import ParserError from pandas.compat import PY3, StringIO, u @@ -151,3 +152,19 @@ def test_quotechar_unicode(self): if PY3: result = self.read_csv(StringIO(data), quotechar=u('\u0001')) tm.assert_frame_equal(result, expected) + + def test_unbalanced_quoting(self): + # see gh-22789. + data = "a,b,c\n1,2,\"3" + + if self.engine == "c": + regex = "EOF inside string starting at row 1" + else: + regex = "unexpected end of data" + + with tm.assert_raises_regex(ParserError, regex): + self.read_csv(StringIO(data)) + + expected = DataFrame([[1, 2, 3]], columns=["a", "b", "c"]) + data = self.read_csv(StringIO(data + '"')) + tm.assert_frame_equal(data, expected) From a936399a539927eb74f06920f5e0a8a71b4cec56 Mon Sep 17 00:00:00 2001 From: Jesper Dramsch Date: Tue, 25 Sep 2018 18:49:12 +0200 Subject: [PATCH 30/85] DOC: Updating str_pad docstring (#22570) --- pandas/core/strings.py | 48 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/pandas/core/strings.py b/pandas/core/strings.py index ed091ce4956bc9..861739f6c694c0 100644 --- a/pandas/core/strings.py +++ b/pandas/core/strings.py @@ -1332,23 +1332,57 @@ def str_index(arr, sub, start=0, end=None, side='left'): def str_pad(arr, width, side='left', fillchar=' '): """ - Pad strings in the Series/Index with an additional character to - specified side. + Pad strings in the Series/Index up to width. Parameters ---------- width : int Minimum width of resulting string; additional characters will be filled - with spaces + with character defined in `fillchar`. side : {'left', 'right', 'both'}, default 'left' - fillchar : str - Additional character for filling, default is whitespace + Side from which to fill resulting string. + fillchar : str, default ' ' + Additional character for filling, default is whitespace. Returns ------- - padded : Series/Index of objects - """ + Series or Index of object + Returns Series or Index with minimum number of char in object. + + See Also + -------- + Series.str.rjust: Fills the left side of strings with an arbitrary + character. Equivalent to ``Series.str.pad(side='left')``. + Series.str.ljust: Fills the right side of strings with an arbitrary + character. Equivalent to ``Series.str.pad(side='right')``. + Series.str.center: Fills boths sides of strings with an arbitrary + character. Equivalent to ``Series.str.pad(side='both')``. + Series.str.zfill: Pad strings in the Series/Index by prepending '0' + character. Equivalent to ``Series.str.pad(side='left', fillchar='0')``. + + Examples + -------- + >>> s = pd.Series(["caribou", "tiger"]) + >>> s + 0 caribou + 1 tiger + dtype: object + + >>> s.str.pad(width=10) + 0 caribou + 1 tiger + dtype: object + >>> s.str.pad(width=10, side='right', fillchar='-') + 0 caribou--- + 1 tiger----- + dtype: object + + >>> s.str.pad(width=10, side='both', fillchar='-') + 0 -caribou-- + 1 --tiger--- + dtype: object + """ if not isinstance(fillchar, compat.string_types): msg = 'fillchar must be a character, not {0}' raise TypeError(msg.format(type(fillchar).__name__)) From 4a459b814e0200faeaaf70c175beb520a28bfa82 Mon Sep 17 00:00:00 2001 From: h-vetinari <33685575+h-vetinari@users.noreply.github.com> Date: Wed, 26 Sep 2018 12:05:37 +0200 Subject: [PATCH 31/85] Fixturize tests/frame/test_arithmetic (#22736) --- pandas/tests/frame/conftest.py | 18 +-- pandas/tests/frame/test_arithmetic.py | 193 ++++++++++---------------- 2 files changed, 84 insertions(+), 127 deletions(-) diff --git a/pandas/tests/frame/conftest.py b/pandas/tests/frame/conftest.py index fdedb93835d752..4a4ce4540b9d5c 100644 --- a/pandas/tests/frame/conftest.py +++ b/pandas/tests/frame/conftest.py @@ -70,9 +70,10 @@ def mixed_float_frame(): Columns are ['A', 'B', 'C', 'D']. """ df = DataFrame(tm.getSeriesData()) - df.A = df.A.astype('float16') + df.A = df.A.astype('float32') df.B = df.B.astype('float32') - df.C = df.C.astype('float64') + df.C = df.C.astype('float16') + df.D = df.D.astype('float64') return df @@ -84,9 +85,10 @@ def mixed_float_frame2(): Columns are ['A', 'B', 'C', 'D']. """ df = DataFrame(tm.getSeriesData()) - df.D = df.D.astype('float16') + df.D = df.D.astype('float32') df.C = df.C.astype('float32') - df.B = df.B.astype('float64') + df.B = df.B.astype('float16') + df.D = df.D.astype('float64') return df @@ -99,10 +101,10 @@ def mixed_int_frame(): """ df = DataFrame({k: v.astype(int) for k, v in compat.iteritems(tm.getSeriesData())}) - df.A = df.A.astype('uint8') - df.B = df.B.astype('int32') - df.C = df.C.astype('int64') - df.D = np.ones(len(df.D), dtype='uint64') + df.A = df.A.astype('int32') + df.B = np.ones(len(df.B), dtype='uint64') + df.C = df.C.astype('uint8') + df.D = df.C.astype('int64') return df diff --git a/pandas/tests/frame/test_arithmetic.py b/pandas/tests/frame/test_arithmetic.py index 9c61f13b944ea5..2b08897864db01 100644 --- a/pandas/tests/frame/test_arithmetic.py +++ b/pandas/tests/frame/test_arithmetic.py @@ -4,8 +4,7 @@ import pytest import numpy as np -from pandas.compat import range, PY3 -import pandas.io.formats.printing as printing +from pandas.compat import range import pandas as pd import pandas.util.testing as tm @@ -127,132 +126,88 @@ def test_df_add_flex_filled_mixed_dtypes(self): 'B': ser * 2}) tm.assert_frame_equal(result, expected) - def test_arith_flex_frame(self): - seriesd = tm.getSeriesData() - frame = pd.DataFrame(seriesd).copy() - - mixed_float = pd.DataFrame({'A': frame['A'].copy().astype('float32'), - 'B': frame['B'].copy().astype('float32'), - 'C': frame['C'].copy().astype('float16'), - 'D': frame['D'].copy().astype('float64')}) - - intframe = pd.DataFrame({k: v.astype(int) - for k, v in seriesd.items()}) - mixed_int = pd.DataFrame({'A': intframe['A'].copy().astype('int32'), - 'B': np.ones(len(intframe), dtype='uint64'), - 'C': intframe['C'].copy().astype('uint8'), - 'D': intframe['D'].copy().astype('int64')}) - - # force these all to int64 to avoid platform testing issues - intframe = pd.DataFrame({c: s for c, s in intframe.items()}, - dtype=np.int64) - - ops = ['add', 'sub', 'mul', 'div', 'truediv', 'pow', 'floordiv', 'mod'] - if not PY3: - aliases = {} - else: - aliases = {'div': 'truediv'} - - for op in ops: - try: - alias = aliases.get(op, op) - f = getattr(operator, alias) - result = getattr(frame, op)(2 * frame) - exp = f(frame, 2 * frame) - tm.assert_frame_equal(result, exp) - - # vs mix float - result = getattr(mixed_float, op)(2 * mixed_float) - exp = f(mixed_float, 2 * mixed_float) - tm.assert_frame_equal(result, exp) - _check_mixed_float(result, dtype=dict(C=None)) - - # vs mix int - if op in ['add', 'sub', 'mul']: - result = getattr(mixed_int, op)(2 + mixed_int) - exp = f(mixed_int, 2 + mixed_int) - - # no overflow in the uint - dtype = None - if op in ['sub']: - dtype = dict(B='uint64', C=None) - elif op in ['add', 'mul']: - dtype = dict(C=None) - tm.assert_frame_equal(result, exp) - _check_mixed_int(result, dtype=dtype) - - # rops - r_f = lambda x, y: f(y, x) - result = getattr(frame, 'r' + op)(2 * frame) - exp = r_f(frame, 2 * frame) - tm.assert_frame_equal(result, exp) - - # vs mix float - result = getattr(mixed_float, op)(2 * mixed_float) - exp = f(mixed_float, 2 * mixed_float) - tm.assert_frame_equal(result, exp) - _check_mixed_float(result, dtype=dict(C=None)) - - result = getattr(intframe, op)(2 * intframe) - exp = f(intframe, 2 * intframe) - tm.assert_frame_equal(result, exp) - - # vs mix int - if op in ['add', 'sub', 'mul']: - result = getattr(mixed_int, op)(2 + mixed_int) - exp = f(mixed_int, 2 + mixed_int) - - # no overflow in the uint - dtype = None - if op in ['sub']: - dtype = dict(B='uint64', C=None) - elif op in ['add', 'mul']: - dtype = dict(C=None) - tm.assert_frame_equal(result, exp) - _check_mixed_int(result, dtype=dtype) - except: - printing.pprint_thing("Failing operation %r" % op) - raise - - # ndim >= 3 - ndim_5 = np.ones(frame.shape + (3, 4, 5)) + def test_arith_flex_frame(self, all_arithmetic_operators, float_frame, + mixed_float_frame): + # one instance of parametrized fixture + op = all_arithmetic_operators + + def f(x, y): + # r-versions not in operator-stdlib; get op without "r" and invert + if op.startswith('__r'): + return getattr(operator, op.replace('__r', '__'))(y, x) + return getattr(operator, op)(x, y) + + result = getattr(float_frame, op)(2 * float_frame) + exp = f(float_frame, 2 * float_frame) + tm.assert_frame_equal(result, exp) + + # vs mix float + result = getattr(mixed_float_frame, op)(2 * mixed_float_frame) + exp = f(mixed_float_frame, 2 * mixed_float_frame) + tm.assert_frame_equal(result, exp) + _check_mixed_float(result, dtype=dict(C=None)) + + @pytest.mark.parametrize('op', ['__add__', '__sub__', '__mul__']) + def test_arith_flex_frame_mixed(self, op, int_frame, mixed_int_frame, + mixed_float_frame): + f = getattr(operator, op) + + # vs mix int + result = getattr(mixed_int_frame, op)(2 + mixed_int_frame) + exp = f(mixed_int_frame, 2 + mixed_int_frame) + + # no overflow in the uint + dtype = None + if op in ['__sub__']: + dtype = dict(B='uint64', C=None) + elif op in ['__add__', '__mul__']: + dtype = dict(C=None) + tm.assert_frame_equal(result, exp) + _check_mixed_int(result, dtype=dtype) + + # vs mix float + result = getattr(mixed_float_frame, op)(2 * mixed_float_frame) + exp = f(mixed_float_frame, 2 * mixed_float_frame) + tm.assert_frame_equal(result, exp) + _check_mixed_float(result, dtype=dict(C=None)) + + # vs plain int + result = getattr(int_frame, op)(2 * int_frame) + exp = f(int_frame, 2 * int_frame) + tm.assert_frame_equal(result, exp) + + def test_arith_flex_frame_raise(self, all_arithmetic_operators, + float_frame): + # one instance of parametrized fixture + op = all_arithmetic_operators + + # Check that arrays with dim >= 3 raise + for dim in range(3, 6): + arr = np.ones((1,) * dim) msg = "Unable to coerce to Series/DataFrame" with tm.assert_raises_regex(ValueError, msg): - f(frame, ndim_5) + getattr(float_frame, op)(arr) - with tm.assert_raises_regex(ValueError, msg): - getattr(frame, op)(ndim_5) - - # res_add = frame.add(frame) - # res_sub = frame.sub(frame) - # res_mul = frame.mul(frame) - # res_div = frame.div(2 * frame) - - # tm.assert_frame_equal(res_add, frame + frame) - # tm.assert_frame_equal(res_sub, frame - frame) - # tm.assert_frame_equal(res_mul, frame * frame) - # tm.assert_frame_equal(res_div, frame / (2 * frame)) + def test_arith_flex_frame_corner(self, float_frame): - const_add = frame.add(1) - tm.assert_frame_equal(const_add, frame + 1) + const_add = float_frame.add(1) + tm.assert_frame_equal(const_add, float_frame + 1) # corner cases - result = frame.add(frame[:0]) - tm.assert_frame_equal(result, frame * np.nan) + result = float_frame.add(float_frame[:0]) + tm.assert_frame_equal(result, float_frame * np.nan) + + result = float_frame[:0].add(float_frame) + tm.assert_frame_equal(result, float_frame * np.nan) - result = frame[:0].add(frame) - tm.assert_frame_equal(result, frame * np.nan) with tm.assert_raises_regex(NotImplementedError, 'fill_value'): - frame.add(frame.iloc[0], fill_value=3) + float_frame.add(float_frame.iloc[0], fill_value=3) + with tm.assert_raises_regex(NotImplementedError, 'fill_value'): - frame.add(frame.iloc[0], axis='index', fill_value=3) - - def test_arith_flex_series(self): - arr = np.array([[1., 2., 3.], - [4., 5., 6.], - [7., 8., 9.]]) - df = pd.DataFrame(arr, columns=['one', 'two', 'three'], - index=['a', 'b', 'c']) + float_frame.add(float_frame.iloc[0], axis='index', fill_value=3) + + def test_arith_flex_series(self, simple_frame): + df = simple_frame row = df.xs('a') col = df['two'] From a393675b992c8b6f9acd8d0d586c008c111d4177 Mon Sep 17 00:00:00 2001 From: Shadi Akiki Date: Wed, 26 Sep 2018 13:20:20 +0300 Subject: [PATCH 32/85] ENH: correlation function accepts method being a callable (#22684) --- doc/source/computation.rst | 15 +++++++++++++ doc/source/whatsnew/v0.24.0.txt | 2 ++ pandas/core/frame.py | 20 +++++++++++++++-- pandas/core/nanops.py | 2 ++ pandas/core/series.py | 18 +++++++++++++-- pandas/tests/series/test_analytics.py | 32 +++++++++++++++++++++++++++ 6 files changed, 85 insertions(+), 4 deletions(-) diff --git a/doc/source/computation.rst b/doc/source/computation.rst index 5e7b8be5f8af04..0d2021de8f88e0 100644 --- a/doc/source/computation.rst +++ b/doc/source/computation.rst @@ -153,6 +153,21 @@ Like ``cov``, ``corr`` also supports the optional ``min_periods`` keyword: frame.corr(min_periods=12) +.. versionadded:: 0.24.0 + +The ``method`` argument can also be a callable for a generic correlation +calculation. In this case, it should be a single function +that produces a single value from two ndarray inputs. Suppose we wanted to +compute the correlation based on histogram intersection: + +.. ipython:: python + + # histogram intersection + histogram_intersection = lambda a, b: np.minimum( + np.true_divide(a, a.sum()), np.true_divide(b, b.sum()) + ).sum() + frame.corr(method=histogram_intersection) + A related method :meth:`~DataFrame.corrwith` is implemented on DataFrame to compute the correlation between like-labeled Series contained in different DataFrame objects. diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 3b61fde77cb9f0..7eb3ea303cc9d1 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -20,6 +20,8 @@ New features - :func:`DataFrame.to_parquet` now accepts ``index`` as an argument, allowing the user to override the engine's default behavior to include or omit the dataframe's indexes from the resulting Parquet file. (:issue:`20768`) +- :meth:`DataFrame.corr` and :meth:`Series.corr` now accept a callable for generic calculation methods of correlation, e.g. histogram intersection (:issue:`22684`) + .. _whatsnew_0240.enhancements.extension_array_operators: diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 721c31c57bc06c..e16f61d7f5f023 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -6672,10 +6672,14 @@ def corr(self, method='pearson', min_periods=1): Parameters ---------- - method : {'pearson', 'kendall', 'spearman'} + method : {'pearson', 'kendall', 'spearman'} or callable * pearson : standard correlation coefficient * kendall : Kendall Tau correlation coefficient * spearman : Spearman rank correlation + * callable: callable with input two 1d ndarrays + and returning a float + .. versionadded:: 0.24.0 + min_periods : int, optional Minimum number of observations required per pair of columns to have a valid result. Currently only available for pearson @@ -6684,6 +6688,18 @@ def corr(self, method='pearson', min_periods=1): Returns ------- y : DataFrame + + Examples + -------- + >>> import numpy as np + >>> histogram_intersection = lambda a, b: np.minimum(a, b + ... ).sum().round(decimals=1) + >>> df = pd.DataFrame([(.2, .3), (.0, .6), (.6, .0), (.2, .1)], + ... columns=['dogs', 'cats']) + >>> df.corr(method=histogram_intersection) + dogs cats + dogs 1.0 0.3 + cats 0.3 1.0 """ numeric_df = self._get_numeric_data() cols = numeric_df.columns @@ -6695,7 +6711,7 @@ def corr(self, method='pearson', min_periods=1): elif method == 'spearman': correl = libalgos.nancorr_spearman(ensure_float64(mat), minp=min_periods) - elif method == 'kendall': + elif method == 'kendall' or callable(method): if min_periods is None: min_periods = 1 mat = ensure_float64(mat).T diff --git a/pandas/core/nanops.py b/pandas/core/nanops.py index f44fb4f6e9e144..7619d47cbc8f9b 100644 --- a/pandas/core/nanops.py +++ b/pandas/core/nanops.py @@ -766,6 +766,8 @@ def nancorr(a, b, method='pearson', min_periods=None): def get_corr_func(method): if method in ['kendall', 'spearman']: from scipy.stats import kendalltau, spearmanr + elif callable(method): + return method def _pearson(a, b): return np.corrcoef(a, b)[0, 1] diff --git a/pandas/core/series.py b/pandas/core/series.py index fdb9ef59c1d3e3..544a08981c83b8 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -1910,10 +1910,14 @@ def corr(self, other, method='pearson', min_periods=None): Parameters ---------- other : Series - method : {'pearson', 'kendall', 'spearman'} + method : {'pearson', 'kendall', 'spearman'} or callable * pearson : standard correlation coefficient * kendall : Kendall Tau correlation coefficient * spearman : Spearman rank correlation + * callable: callable with input two 1d ndarray + and returning a float + .. versionadded:: 0.24.0 + min_periods : int, optional Minimum number of observations needed to have a valid result @@ -1921,12 +1925,22 @@ def corr(self, other, method='pearson', min_periods=None): Returns ------- correlation : float + + Examples + -------- + >>> import numpy as np + >>> histogram_intersection = lambda a, b: np.minimum(a, b + ... ).sum().round(decimals=1) + >>> s1 = pd.Series([.2, .0, .6, .2]) + >>> s2 = pd.Series([.3, .6, .0, .1]) + >>> s1.corr(s2, method=histogram_intersection) + 0.3 """ this, other = self.align(other, join='inner', copy=False) if len(this) == 0: return np.nan - if method in ['pearson', 'spearman', 'kendall']: + if method in ['pearson', 'spearman', 'kendall'] or callable(method): return nanops.nancorr(this.values, other.values, method=method, min_periods=min_periods) diff --git a/pandas/tests/series/test_analytics.py b/pandas/tests/series/test_analytics.py index 9acd6501c38251..58a160d17cbe8d 100644 --- a/pandas/tests/series/test_analytics.py +++ b/pandas/tests/series/test_analytics.py @@ -789,6 +789,38 @@ def test_corr_invalid_method(self): with tm.assert_raises_regex(ValueError, msg): s1.corr(s2, method="____") + def test_corr_callable_method(self): + # simple correlation example + # returns 1 if exact equality, 0 otherwise + my_corr = lambda a, b: 1. if (a == b).all() else 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( + self.ts.corr(self.ts, method=my_corr), 1.) + + # partial overlap + tm.assert_almost_equal( + self.ts[:15].corr(self.ts[5:], method=my_corr), 1.) + + # No overlap + assert np.isnan( + self.ts[::2].corr(self.ts[1::2], method=my_corr)) + + # dataframe example + df = pd.DataFrame([s1, s2]) + expected = pd.DataFrame([ + {0: 1., 1: 0}, {0: 0, 1: 1.}]) + tm.assert_almost_equal( + df.transpose().corr(method=my_corr), expected) + def test_cov(self): # full overlap tm.assert_almost_equal(self.ts.cov(self.ts), self.ts.std() ** 2) From 739e6be23431ae5263ebbc0c35d30c0afd05951f Mon Sep 17 00:00:00 2001 From: William Ayd Date: Wed, 26 Sep 2018 05:35:54 -0500 Subject: [PATCH 33/85] Fixed Issue Preventing Agg on RollingGroupBy Objects (#21323) --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/core/base.py | 4 +-- pandas/core/groupby/base.py | 9 +++++- pandas/tests/groupby/test_groupby.py | 10 ++++-- pandas/tests/test_window.py | 48 ++++++++++++++++++++++++++++ 5 files changed, 67 insertions(+), 5 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 7eb3ea303cc9d1..0e591e180e0781 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -785,6 +785,7 @@ Groupby/Resample/Rolling - Bug in :meth:`Series.resample` when passing ``numpy.timedelta64`` to ``loffset`` kwarg (:issue:`7687`). - Bug in :meth:`Resampler.asfreq` when frequency of ``TimedeltaIndex`` is a subperiod of a new frequency (:issue:`13022`). - Bug in :meth:`SeriesGroupBy.mean` when values were integral but could not fit inside of int64, overflowing instead. (:issue:`22487`) +- :func:`RollingGroupby.agg` and :func:`ExpandingGroupby.agg` now support multiple aggregation functions as parameters (:issue:`15072`) Sparse ^^^^^^ diff --git a/pandas/core/base.py b/pandas/core/base.py index 26fea89b45ae1f..7f14a68503973e 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -245,8 +245,8 @@ def _obj_with_exclusions(self): def __getitem__(self, key): if self._selection is not None: - raise Exception('Column(s) {selection} already selected' - .format(selection=self._selection)) + raise IndexError('Column(s) {selection} already selected' + .format(selection=self._selection)) if isinstance(key, (list, tuple, ABCSeries, ABCIndexClass, np.ndarray)): diff --git a/pandas/core/groupby/base.py b/pandas/core/groupby/base.py index 96c74f7fd4d75a..ac84971de08d8c 100644 --- a/pandas/core/groupby/base.py +++ b/pandas/core/groupby/base.py @@ -44,8 +44,15 @@ def _gotitem(self, key, ndim, subset=None): # we need to make a shallow copy of ourselves # with the same groupby kwargs = {attr: getattr(self, attr) for attr in self._attributes} + + # Try to select from a DataFrame, falling back to a Series + try: + groupby = self._groupby[key] + except IndexError: + groupby = self._groupby + self = self.__class__(subset, - groupby=self._groupby[key], + groupby=groupby, parent=self, **kwargs) self._reset_cache() diff --git a/pandas/tests/groupby/test_groupby.py b/pandas/tests/groupby/test_groupby.py index 483f814bc83835..3cdd0965ccfd0d 100644 --- a/pandas/tests/groupby/test_groupby.py +++ b/pandas/tests/groupby/test_groupby.py @@ -623,8 +623,14 @@ def test_as_index_series_return_frame(df): assert isinstance(result2, DataFrame) assert_frame_equal(result2, expected2) - # corner case - pytest.raises(Exception, grouped['C'].__getitem__, 'D') + +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 tm.assert_raises_regex(IndexError, msg): + grouped['C'].__getitem__('D') def test_groupby_as_index_cython(df): diff --git a/pandas/tests/test_window.py b/pandas/tests/test_window.py index 052bfd2b858fb2..cc663fc59cbf16 100644 --- a/pandas/tests/test_window.py +++ b/pandas/tests/test_window.py @@ -1,3 +1,4 @@ +from collections import OrderedDict from itertools import product import pytest import warnings @@ -314,6 +315,53 @@ def test_preserve_metadata(self): assert s2.name == 'foo' assert s3.name == 'foo' + @pytest.mark.parametrize("func,window_size,expected_vals", [ + ('rolling', 2, [[np.nan, np.nan, np.nan, np.nan], + [15., 20., 25., 20.], + [25., 30., 35., 30.], + [np.nan, np.nan, np.nan, np.nan], + [20., 30., 35., 30.], + [35., 40., 60., 40.], + [60., 80., 85., 80]]), + ('expanding', None, [[10., 10., 20., 20.], + [15., 20., 25., 20.], + [20., 30., 30., 20.], + [10., 10., 30., 30.], + [20., 30., 35., 30.], + [26.666667, 40., 50., 30.], + [40., 80., 60., 30.]])]) + def test_multiple_agg_funcs(self, func, window_size, expected_vals): + # GH 15072 + df = pd.DataFrame([ + ['A', 10, 20], + ['A', 20, 30], + ['A', 30, 40], + ['B', 10, 30], + ['B', 30, 40], + ['B', 40, 80], + ['B', 80, 90]], columns=['stock', 'low', 'high']) + + f = getattr(df.groupby('stock'), func) + if window_size: + window = f(window_size) + else: + window = f() + + index = pd.MultiIndex.from_tuples([ + ('A', 0), ('A', 1), ('A', 2), + ('B', 3), ('B', 4), ('B', 5), ('B', 6)], names=['stock', None]) + columns = pd.MultiIndex.from_tuples([ + ('low', 'mean'), ('low', 'max'), ('high', 'mean'), + ('high', 'min')]) + expected = pd.DataFrame(expected_vals, index=index, columns=columns) + + result = window.agg(OrderedDict(( + ('low', ['mean', 'max']), + ('high', ['mean', 'min']), + ))) + + tm.assert_frame_equal(result, expected) + @pytest.mark.filterwarnings("ignore:can't resolve package:ImportWarning") class TestWindow(Base): From f64f9940265baeba3658fa5ad87f03d7d5ad25a8 Mon Sep 17 00:00:00 2001 From: HubertKl <39779339+HubertKl@users.noreply.github.com> Date: Wed, 26 Sep 2018 12:50:52 +0100 Subject: [PATCH 34/85] DOC: Updating Series.autocorr docstring (#22787) --- pandas/core/series.py | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/pandas/core/series.py b/pandas/core/series.py index 544a08981c83b8..59fb019af9b1c9 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -2035,7 +2035,10 @@ def diff(self, periods=1): def autocorr(self, lag=1): """ - Lag-N autocorrelation + Compute the lag-N autocorrelation. + + This method computes the Pearson correlation between + the Series and its shifted self. Parameters ---------- @@ -2044,7 +2047,34 @@ def autocorr(self, lag=1): Returns ------- - autocorr : float + float + The Pearson correlation between self and self.shift(lag). + + See Also + -------- + Series.corr : Compute the correlation between two Series. + Series.shift : Shift index by desired number of periods. + DataFrame.corr : Compute pairwise correlation of columns. + DataFrame.corrwith : Compute pairwise correlation between rows or + columns of two DataFrame objects. + + Notes + ----- + If the Pearson correlation is not well defined return 'NaN'. + + Examples + -------- + >>> s = pd.Series([0.25, 0.5, 0.2, -0.05]) + >>> s.autocorr() + 0.1035526330902407 + >>> s.autocorr(lag=2) + -0.9999999999999999 + + If the Pearson correlation is not well defined, then 'NaN' is returned. + + >>> s = pd.Series([1, 0, 0, 0]) + >>> s.autocorr() + nan """ return self.corr(self.shift(lag)) From 9df8065ab3c1f0dd53d10185d40d7e49d00a92df Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 26 Sep 2018 09:27:52 -0500 Subject: [PATCH 35/85] Preserve Extension type on cross section (#22785) --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/core/base.py | 6 ++-- pandas/core/frame.py | 11 ++++--- pandas/core/indexes/multi.py | 12 ++++--- pandas/core/internals/managers.py | 41 +++++++++++++++++------- pandas/tests/frame/test_dtypes.py | 12 +++++-- pandas/tests/indexing/test_indexing.py | 28 ++++++++++++++++ pandas/tests/indexing/test_multiindex.py | 4 +-- pandas/tests/series/test_dtypes.py | 8 ++--- 9 files changed, 91 insertions(+), 32 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 0e591e180e0781..707257a35983e4 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -500,6 +500,7 @@ ExtensionType Changes - :meth:`Series.combine()` works correctly with :class:`~pandas.api.extensions.ExtensionArray` inside of :class:`Series` (:issue:`20825`) - :meth:`Series.combine()` with scalar argument now works for any function type (:issue:`21248`) - :meth:`Series.astype` and :meth:`DataFrame.astype` now dispatch to :meth:`ExtensionArray.astype` (:issue:`21185:`). +- Slicing a single row of a ``DataFrame`` with multiple ExtensionArrays of the same type now preserves the dtype, rather than coercing to object (:issue:`22784`) - Added :meth:`pandas.api.types.register_extension_dtype` to register an extension type with pandas (:issue:`22664`) .. _whatsnew_0240.api.incompatibilities: diff --git a/pandas/core/base.py b/pandas/core/base.py index 7f14a68503973e..00c049497c0d80 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -664,7 +664,7 @@ def transpose(self, *args, **kwargs): "definition self") @property - def _is_homogeneous(self): + def _is_homogeneous_type(self): """Whether the object has a single dtype. By definition, Series and Index are always considered homogeneous. @@ -673,8 +673,8 @@ def _is_homogeneous(self): See Also -------- - DataFrame._is_homogeneous - MultiIndex._is_homogeneous + DataFrame._is_homogeneous_type + MultiIndex._is_homogeneous_type """ return True diff --git a/pandas/core/frame.py b/pandas/core/frame.py index e16f61d7f5f023..cc58674398b70e 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -614,7 +614,7 @@ def shape(self): return len(self.index), len(self.columns) @property - def _is_homogeneous(self): + def _is_homogeneous_type(self): """ Whether all the columns in a DataFrame have the same type. @@ -624,16 +624,17 @@ def _is_homogeneous(self): Examples -------- - >>> DataFrame({"A": [1, 2], "B": [3, 4]})._is_homogeneous + >>> DataFrame({"A": [1, 2], "B": [3, 4]})._is_homogeneous_type True - >>> DataFrame({"A": [1, 2], "B": [3.0, 4.0]})._is_homogeneous + >>> DataFrame({"A": [1, 2], "B": [3.0, 4.0]})._is_homogeneous_type False Items with the same type but different sizes are considered different types. - >>> DataFrame({"A": np.array([1, 2], dtype=np.int32), - ... "B": np.array([1, 2], dtype=np.int64)})._is_homogeneous + >>> DataFrame({ + ... "A": np.array([1, 2], dtype=np.int32), + ... "B": np.array([1, 2], dtype=np.int64)})._is_homogeneous_type False """ if self._data.any_extension_types: diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index ad38f037b6578d..3e6b934e1e8632 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -289,21 +289,23 @@ def levels(self): return self._levels @property - def _is_homogeneous(self): + def _is_homogeneous_type(self): """Whether the levels of a MultiIndex all have the same dtype. This looks at the dtypes of the levels. See Also -------- - Index._is_homogeneous - DataFrame._is_homogeneous + Index._is_homogeneous_type + DataFrame._is_homogeneous_type Examples -------- - >>> MultiIndex.from_tuples([('a', 'b'), ('a', 'c')])._is_homogeneous + >>> MultiIndex.from_tuples([ + ... ('a', 'b'), ('a', 'c')])._is_homogeneous_type True - >>> MultiIndex.from_tuples([('a', 1), ('a', 2)])._is_homogeneous + >>> MultiIndex.from_tuples([ + ... ('a', 1), ('a', 2)])._is_homogeneous_type False """ return len({x.dtype for x in self.levels}) <= 1 diff --git a/pandas/core/internals/managers.py b/pandas/core/internals/managers.py index 63738594799f54..2f29f1ae2509fc 100644 --- a/pandas/core/internals/managers.py +++ b/pandas/core/internals/managers.py @@ -12,9 +12,6 @@ from pandas.util._validators import validate_bool_kwarg from pandas.compat import range, map, zip -from pandas.core.dtypes.dtypes import ( - ExtensionDtype, - PandasExtensionDtype) from pandas.core.dtypes.common import ( _NS_DTYPE, is_datetimelike_v_numeric, @@ -791,6 +788,11 @@ def _interleave(self): """ dtype = _interleaved_dtype(self.blocks) + if is_extension_array_dtype(dtype): + # TODO: https://github.com/pandas-dev/pandas/issues/22791 + # Give EAs some input on what happens here. Sparse needs this. + dtype = 'object' + result = np.empty(self.shape, dtype=dtype) if result.shape[0] == 0: @@ -906,14 +908,25 @@ def fast_xs(self, loc): # unique dtype = _interleaved_dtype(self.blocks) + n = len(items) - result = np.empty(n, dtype=dtype) + if is_extension_array_dtype(dtype): + # we'll eventually construct an ExtensionArray. + result = np.empty(n, dtype=object) + else: + result = np.empty(n, dtype=dtype) + for blk in self.blocks: # Such assignment may incorrectly coerce NaT to None # result[blk.mgr_locs] = blk._slice((slice(None), loc)) for i, rl in enumerate(blk.mgr_locs): result[rl] = blk._try_coerce_result(blk.iget((i, loc))) + if is_extension_array_dtype(dtype): + result = dtype.construct_array_type()._from_sequence( + result, dtype=dtype + ) + return result def consolidate(self): @@ -1855,16 +1868,22 @@ def _shape_compat(x): def _interleaved_dtype(blocks): - if not len(blocks): - return None + # type: (List[Block]) -> Optional[Union[np.dtype, ExtensionDtype]] + """Find the common dtype for `blocks`. - dtype = find_common_type([b.dtype for b in blocks]) + Parameters + ---------- + blocks : List[Block] - # only numpy compat - if isinstance(dtype, (PandasExtensionDtype, ExtensionDtype)): - dtype = np.object + Returns + ------- + dtype : Optional[Union[np.dtype, ExtensionDtype]] + None is returned when `blocks` is empty. + """ + if not len(blocks): + return None - return dtype + return find_common_type([b.dtype for b in blocks]) def _consolidate(blocks): diff --git a/pandas/tests/frame/test_dtypes.py b/pandas/tests/frame/test_dtypes.py index ca4bd64659e069..c91370dc367707 100644 --- a/pandas/tests/frame/test_dtypes.py +++ b/pandas/tests/frame/test_dtypes.py @@ -836,8 +836,16 @@ def test_constructor_list_str_na(self, string_dtype): "B": pd.Categorical(['b', 'c'])}), False), ]) - def test_is_homogeneous(self, data, expected): - assert data._is_homogeneous is expected + def test_is_homogeneous_type(self, data, expected): + assert data._is_homogeneous_type is expected + + def test_asarray_homogenous(self): + df = pd.DataFrame({"A": pd.Categorical([1, 2]), + "B": pd.Categorical([1, 2])}) + result = np.asarray(df) + # may change from object in the future + expected = np.array([[1, 1], [2, 2]], dtype='object') + tm.assert_numpy_array_equal(result, expected) class TestDataFrameDatetimeWithTZ(TestData): diff --git a/pandas/tests/indexing/test_indexing.py b/pandas/tests/indexing/test_indexing.py index 761c633f89da33..0f524ca0aaac5b 100644 --- a/pandas/tests/indexing/test_indexing.py +++ b/pandas/tests/indexing/test_indexing.py @@ -1079,3 +1079,31 @@ def test_validate_indices_high(): def test_validate_indices_empty(): with tm.assert_raises_regex(IndexError, "indices are out"): validate_indices(np.array([0, 1]), 0) + + +def test_extension_array_cross_section(): + # A cross-section of a homogeneous EA should be an EA + df = pd.DataFrame({ + "A": pd.core.arrays.integer_array([1, 2]), + "B": pd.core.arrays.integer_array([3, 4]) + }, index=['a', 'b']) + expected = pd.Series(pd.core.arrays.integer_array([1, 3]), + 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(): + df = pd.DataFrame({ + "A": pd.core.arrays.integer_array([1, 2]), + "B": np.array([1, 2]), + }, index=['a', 'b']) + result = df.loc['a'] + expected = pd.Series([1, 1], dtype=object, index=['A', 'B'], name='a') + tm.assert_series_equal(result, expected) + + result = df.iloc[0] + tm.assert_series_equal(result, expected) diff --git a/pandas/tests/indexing/test_multiindex.py b/pandas/tests/indexing/test_multiindex.py index aefa8badf72e79..b8f80164e54022 100644 --- a/pandas/tests/indexing/test_multiindex.py +++ b/pandas/tests/indexing/test_multiindex.py @@ -738,8 +738,8 @@ def test_multiindex_contains_dropped(self): (MultiIndex.from_product([(1, 2), (3, 4)]), True), (MultiIndex.from_product([('a', 'b'), (1, 2)]), False), ]) - def test_multiindex_is_homogeneous(self, data, expected): - assert data._is_homogeneous is expected + def test_multiindex_is_homogeneous_type(self, data, expected): + assert data._is_homogeneous_type is expected class TestMultiIndexSlicers(object): diff --git a/pandas/tests/series/test_dtypes.py b/pandas/tests/series/test_dtypes.py index 83a458eedbd93a..125dff9ecfa7c7 100644 --- a/pandas/tests/series/test_dtypes.py +++ b/pandas/tests/series/test_dtypes.py @@ -509,7 +509,7 @@ def test_infer_objects_series(self): assert actual.dtype == 'object' tm.assert_series_equal(actual, expected) - def test_is_homogeneous(self): - assert Series()._is_homogeneous - assert Series([1, 2])._is_homogeneous - assert Series(pd.Categorical([1, 2]))._is_homogeneous + def test_is_homogeneous_type(self): + assert Series()._is_homogeneous_type + assert Series([1, 2])._is_homogeneous_type + assert Series(pd.Categorical([1, 2]))._is_homogeneous_type From a03d9535b16a6d5441334ef2e698d72778cf8115 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 26 Sep 2018 14:03:56 -0500 Subject: [PATCH 36/85] DOC: Fix warnings in doc build (#22838) --- doc/source/api.rst | 9 +++++++ doc/source/basics.rst | 2 +- doc/source/cookbook.rst | 6 ++--- doc/source/ecosystem.rst | 8 +++--- doc/source/io.rst | 29 ++++++++++----------- doc/source/text.rst | 5 ++-- doc/source/timeseries.rst | 45 +++++++++++++++++++-------------- doc/source/whatsnew/v0.18.0.txt | 2 +- doc/source/whatsnew/v0.20.0.txt | 2 +- doc/source/whatsnew/v0.24.0.txt | 9 ++++--- pandas/core/generic.py | 18 +++++++++---- pandas/core/series.py | 10 +++++--- pandas/core/window.py | 6 ++--- pandas/io/formats/style.py | 1 + 14 files changed, 88 insertions(+), 64 deletions(-) diff --git a/doc/source/api.rst b/doc/source/api.rst index e4b055c14ec27d..073ed8a082a111 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -2603,3 +2603,12 @@ objects. generated/pandas.Series.ix generated/pandas.Series.imag generated/pandas.Series.real + + +.. Can't convince sphinx to generate toctree for this class attribute. +.. So we do it manually to avoid a warning + +.. toctree:: + :hidden: + + generated/pandas.api.extensions.ExtensionDtype.na_value diff --git a/doc/source/basics.rst b/doc/source/basics.rst index c18b94fea9a28d..6eeb97349100a0 100644 --- a/doc/source/basics.rst +++ b/doc/source/basics.rst @@ -1935,7 +1935,7 @@ NumPy's type-system for a few cases. * :ref:`Categorical ` * :ref:`Datetime with Timezone ` * :ref:`Period ` -* :ref:`Interval ` +* :ref:`Interval ` Pandas uses the ``object`` dtype for storing strings. diff --git a/doc/source/cookbook.rst b/doc/source/cookbook.rst index f6fa9e9f861437..a4dc99383a5625 100644 --- a/doc/source/cookbook.rst +++ b/doc/source/cookbook.rst @@ -505,13 +505,11 @@ Unlike agg, apply's callable is passed a sub-DataFrame which gives you access to .. ipython:: python df = pd.DataFrame({'A' : [1, 1, 2, 2], 'B' : [1, -1, 1, 2]}) - gb = df.groupby('A') def replace(g): - mask = g < 0 - g.loc[mask] = g[~mask].mean() - return g + mask = g < 0 + return g.where(mask, g[~mask].mean()) gb.transform(replace) diff --git a/doc/source/ecosystem.rst b/doc/source/ecosystem.rst index 1014982fea21a1..7fffcadd8ee8c0 100644 --- a/doc/source/ecosystem.rst +++ b/doc/source/ecosystem.rst @@ -73,8 +73,8 @@ large data to thin clients. `seaborn `__ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Seaborn is a Python visualization library based on `matplotlib -`__. It provides a high-level, dataset-oriented +Seaborn is a Python visualization library based on +`matplotlib `__. It provides a high-level, dataset-oriented interface for creating attractive statistical graphics. The plotting functions in seaborn understand pandas objects and leverage pandas grouping operations internally to support concise specification of complex visualizations. Seaborn @@ -140,7 +140,7 @@ which are utilized by Jupyter Notebook for displaying (Note: HTML tables may or may not be compatible with non-HTML Jupyter output formats.) -See :ref:`Options and Settings ` and :ref:`` +See :ref:`Options and Settings ` and :ref:`options.available ` for pandas ``display.`` settings. `quantopian/qgrid `__ @@ -169,7 +169,7 @@ or the clipboard into a new pandas DataFrame via a sophisticated import wizard. Most pandas classes, methods and data attributes can be autocompleted in Spyder's `Editor `__ and `IPython Console `__, -and Spyder's `Help pane`__ can retrieve +and Spyder's `Help pane `__ can retrieve and render Numpydoc documentation on pandas objects in rich text with Sphinx both automatically and on-demand. diff --git a/doc/source/io.rst b/doc/source/io.rst index cb22bb9198e258..039cba2993381d 100644 --- a/doc/source/io.rst +++ b/doc/source/io.rst @@ -66,16 +66,13 @@ The pandas I/O API is a set of top level ``reader`` functions accessed like CSV & Text files ---------------- -The two workhorse functions for reading text files (a.k.a. flat files) are -:func:`read_csv` and :func:`read_table`. They both use the same parsing code to -intelligently convert tabular data into a ``DataFrame`` object. See the -:ref:`cookbook` for some advanced strategies. +The workhorse function for reading text files (a.k.a. flat files) is +:func:`read_csv`. See the :ref:`cookbook` for some advanced strategies. Parsing options ''''''''''''''' -The functions :func:`read_csv` and :func:`read_table` accept the following -common arguments: +:func:`read_csv` accepts the following common arguments: Basic +++++ @@ -780,8 +777,8 @@ Date Handling Specifying Date Columns +++++++++++++++++++++++ -To better facilitate working with datetime data, :func:`read_csv` and -:func:`read_table` use the keyword arguments ``parse_dates`` and ``date_parser`` +To better facilitate working with datetime data, :func:`read_csv` +uses the keyword arguments ``parse_dates`` and ``date_parser`` to allow users to specify a variety of columns and date/time formats to turn the input text data into ``datetime`` objects. @@ -1434,7 +1431,7 @@ Suppose you have data indexed by two columns: print(open('data/mindex_ex.csv').read()) -The ``index_col`` argument to ``read_csv`` and ``read_table`` can take a list of +The ``index_col`` argument to ``read_csv`` can take a list of column numbers to turn multiple columns into a ``MultiIndex`` for the index of the returned object: @@ -1505,8 +1502,8 @@ class of the csv module. For this, you have to specify ``sep=None``. .. ipython:: python - print(open('tmp2.sv').read()) - pd.read_csv('tmp2.sv', sep=None, engine='python') + print(open('tmp2.sv').read()) + pd.read_csv('tmp2.sv', sep=None, engine='python') .. _io.multiple_files: @@ -1528,16 +1525,16 @@ rather than reading the entire file into memory, such as the following: .. ipython:: python print(open('tmp.sv').read()) - table = pd.read_table('tmp.sv', sep='|') + table = pd.read_csv('tmp.sv', sep='|') table -By specifying a ``chunksize`` to ``read_csv`` or ``read_table``, the return +By specifying a ``chunksize`` to ``read_csv``, the return value will be an iterable object of type ``TextFileReader``: .. ipython:: python - reader = pd.read_table('tmp.sv', sep='|', chunksize=4) + reader = pd.read_csv('tmp.sv', sep='|', chunksize=4) reader for chunk in reader: @@ -1548,7 +1545,7 @@ Specifying ``iterator=True`` will also return the ``TextFileReader`` object: .. ipython:: python - reader = pd.read_table('tmp.sv', sep='|', iterator=True) + reader = pd.read_csv('tmp.sv', sep='|', iterator=True) reader.get_chunk(5) .. ipython:: python @@ -3067,7 +3064,7 @@ Clipboard A handy way to grab data is to use the :meth:`~DataFrame.read_clipboard` method, which takes the contents of the clipboard buffer and passes them to the -``read_table`` method. For instance, you can copy the following text to the +``read_csv`` method. For instance, you can copy the following text to the clipboard (CTRL-C on many operating systems): .. code-block:: python diff --git a/doc/source/text.rst b/doc/source/text.rst index 61583a179e5729..d01c48695d0d66 100644 --- a/doc/source/text.rst +++ b/doc/source/text.rst @@ -312,14 +312,15 @@ All one-dimensional list-likes can be combined in a list-like container (includi s u - s.str.cat([u.values, ['A', 'B', 'C', 'D'], map(str, u.index)], na_rep='-') + s.str.cat([u.values, + u.index.astype(str).values], na_rep='-') All elements must match in length to the calling ``Series`` (or ``Index``), except those having an index if ``join`` is not None: .. ipython:: python v - s.str.cat([u, v, ['A', 'B', 'C', 'D']], join='outer', na_rep='-') + s.str.cat([u, v], join='outer', na_rep='-') If using ``join='right'`` on a list of ``others`` that contains different indexes, the union of these indexes will be used as the basis for the final concatenation: diff --git a/doc/source/timeseries.rst b/doc/source/timeseries.rst index 71bc064ffb0c21..85b0abe421eb20 100644 --- a/doc/source/timeseries.rst +++ b/doc/source/timeseries.rst @@ -753,18 +753,28 @@ regularity will result in a ``DatetimeIndex``, although frequency is lost: Iterating through groups ------------------------ -With the :ref:`Resampler` object in hand, iterating through the grouped data is very +With the ``Resampler`` object in hand, iterating through the grouped data is very natural and functions similarly to :py:func:`itertools.groupby`: .. ipython:: python - resampled = df.resample('H') + small = pd.Series( + range(6), + index=pd.to_datetime(['2017-01-01T00:00:00', + '2017-01-01T00:30:00', + '2017-01-01T00:31:00', + '2017-01-01T01:00:00', + '2017-01-01T03:00:00', + '2017-01-01T03:05:00']) + ) + resampled = small.resample('H') for name, group in resampled: - print(name) - print(group) + print("Group: ", name) + print("-" * 27) + print(group, end="\n\n") -See :ref:`groupby.iterating-label`. +See :ref:`groupby.iterating-label` or :class:`Resampler.__iter__` for more. .. _timeseries.components: @@ -910,26 +920,22 @@ It's definitely worth exploring the ``pandas.tseries.offsets`` module and the various docstrings for the classes. These operations (``apply``, ``rollforward`` and ``rollback``) preserve time -(hour, minute, etc) information by default. To reset time, use ``normalize=True`` -when creating the offset instance. If ``normalize=True``, the result is -normalized after the function is applied. - +(hour, minute, etc) information by default. To reset time, use ``normalize`` +before or after applying the operation (depending on whether you want the +time information included in the operation. .. ipython:: python + ts = pd.Timestamp('2014-01-01 09:00') day = Day() - day.apply(pd.Timestamp('2014-01-01 09:00')) - - day = Day(normalize=True) - day.apply(pd.Timestamp('2014-01-01 09:00')) + day.apply(ts) + day.apply(ts).normalize() + ts = pd.Timestamp('2014-01-01 22:00') hour = Hour() - hour.apply(pd.Timestamp('2014-01-01 22:00')) - - hour = Hour(normalize=True) - hour.apply(pd.Timestamp('2014-01-01 22:00')) - hour.apply(pd.Timestamp('2014-01-01 23:00')) - + hour.apply(ts) + hour.apply(ts).normalize() + hour.apply(pd.Timestamp("2014-01-01 23:30")).normalize() .. _timeseries.dayvscalendarday: @@ -1488,6 +1494,7 @@ time. The method for this is :meth:`~Series.shift`, which is available on all of the pandas objects. .. ipython:: python + ts = pd.Series(range(len(rng)), index=rng) ts = ts[:5] ts.shift(1) diff --git a/doc/source/whatsnew/v0.18.0.txt b/doc/source/whatsnew/v0.18.0.txt index a3213136d998ad..e38ba54d4b0581 100644 --- a/doc/source/whatsnew/v0.18.0.txt +++ b/doc/source/whatsnew/v0.18.0.txt @@ -373,7 +373,7 @@ New Behavior: s = pd.Series([1,2,3], index=np.arange(3.)) s s.index - print(s.to_csv(path=None)) + print(s.to_csv(path_or_buf=None, header=False)) Changes to dtype assignment behaviors ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/source/whatsnew/v0.20.0.txt b/doc/source/whatsnew/v0.20.0.txt index 3c0818343208a2..9f5fbdc195f343 100644 --- a/doc/source/whatsnew/v0.20.0.txt +++ b/doc/source/whatsnew/v0.20.0.txt @@ -186,7 +186,7 @@ Previously, only ``gzip`` compression was supported. By default, compression of URLs and paths are now inferred using their file extensions. Additionally, support for bz2 compression in the python 2 C-engine improved (:issue:`14874`). -.. ipython:: python +.. code-block:: python url = 'https://github.com/{repo}/raw/{branch}/{path}'.format( repo = 'pandas-dev/pandas', diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 707257a35983e4..481c31d2410a99 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -253,7 +253,6 @@ UTC offset (:issue:`17697`, :issue:`11736`, :issue:`22457`) .. code-block:: ipython - In [2]: pd.to_datetime("2015-11-18 15:30:00+05:30") Out[2]: Timestamp('2015-11-18 10:00:00') @@ -291,6 +290,7 @@ Passing ``utc=True`` will mimic the previous behavior but will correctly indicat that the dates have been converted to UTC .. ipython:: python + pd.to_datetime(["2015-11-18 15:30:00+05:30", "2015-11-18 16:30:00+06:30"], utc=True) .. _whatsnew_0240.api_breaking.calendarday: @@ -457,7 +457,7 @@ Previous Behavior: Out[3]: Int64Index([0, 1, 2], dtype='int64') -.. _whatsnew_0240.api.timedelta64_subtract_nan +.. _whatsnew_0240.api.timedelta64_subtract_nan: Addition/Subtraction of ``NaN`` from :class:`DataFrame` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -468,9 +468,10 @@ all-``NaT``. This is for compatibility with ``TimedeltaIndex`` and ``Series`` behavior (:issue:`22163`) .. ipython:: python + :okexcept: - df = pd.DataFrame([pd.Timedelta(days=1)]) - df - np.nan + df = pd.DataFrame([pd.Timedelta(days=1)]) + df - np.nan Previous Behavior: diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 19ac4b49358d4f..393e7caae5fabb 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -2060,10 +2060,12 @@ def to_json(self, path_or_buf=None, orient=None, date_format=None, like. .. versionadded:: 0.19.0 - compression : {'infer', 'gzip', 'bz2', 'zip', 'xz', None}, - default 'infer' + + compression : {'infer', 'gzip', 'bz2', 'zip', 'xz', None} + A string representing the compression to use in the output file, - only used when the first argument is a filename. + only used when the first argument is a filename. By default, the + compression is inferred from the filename. .. versionadded:: 0.21.0 .. versionchanged:: 0.24.0 @@ -9514,7 +9516,9 @@ def to_csv(self, path_or_buf=None, sep=",", na_rep='', float_format=None, a string. .. versionchanged:: 0.24.0 - Was previously named "path" for Series. + + Was previously named "path" for Series. + sep : str, default ',' String of length 1. Field delimiter for the output file. na_rep : str, default '' @@ -9528,7 +9532,9 @@ def to_csv(self, path_or_buf=None, sep=",", na_rep='', float_format=None, assumed to be aliases for the column names. .. versionchanged:: 0.24.0 - Previously defaulted to False for Series. + + Previously defaulted to False for Series. + index : bool, default True Write row names (index). index_label : str or sequence, or False, default None @@ -9550,7 +9556,9 @@ def to_csv(self, path_or_buf=None, sep=",", na_rep='', float_format=None, compression). .. versionchanged:: 0.24.0 + 'infer' option added and set to default. + quoting : optional constant from csv module Defaults to csv.QUOTE_MINIMAL. If you have set a `float_format` then floats are converted to strings and thus csv.QUOTE_NONNUMERIC diff --git a/pandas/core/series.py b/pandas/core/series.py index 59fb019af9b1c9..83f80c305c5ebf 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -2065,10 +2065,10 @@ def autocorr(self, lag=1): Examples -------- >>> s = pd.Series([0.25, 0.5, 0.2, -0.05]) - >>> s.autocorr() - 0.1035526330902407 - >>> s.autocorr(lag=2) - -0.9999999999999999 + >>> s.autocorr() # doctest: +ELLIPSIS + 0.10355... + >>> s.autocorr(lag=2) # doctest: +ELLIPSIS + -0.99999... If the Pearson correlation is not well defined, then 'NaN' is returned. @@ -2789,6 +2789,7 @@ def nlargest(self, n=5, keep='first'): keep : {'first', 'last', 'all'}, default 'first' When there are duplicate values that cannot all fit in a Series of `n` elements: + - ``first`` : take the first occurrences based on the index order - ``last`` : take the last occurrences based on the index order - ``all`` : keep all occurrences. This can result in a Series of @@ -2884,6 +2885,7 @@ def nsmallest(self, n=5, keep='first'): keep : {'first', 'last', 'all'}, default 'first' When there are duplicate values that cannot all fit in a Series of `n` elements: + - ``first`` : take the first occurrences based on the index order - ``last`` : take the last occurrences based on the index order - ``all`` : keep all occurrences. This can result in a Series of diff --git a/pandas/core/window.py b/pandas/core/window.py index 66f48f403c941e..5cdf62d5a55372 100644 --- a/pandas/core/window.py +++ b/pandas/core/window.py @@ -1404,7 +1404,7 @@ def _get_cov(X, Y): otherwise defaults to `False`. Not relevant for :class:`~pandas.Series`. **kwargs - Under Review. + Unused. Returns ------- @@ -1430,7 +1430,7 @@ def _get_cov(X, Y): all 1's), except for :class:`~pandas.DataFrame` inputs with `pairwise` set to `True`. - Function will return `NaN`s for correlations of equal valued sequences; + Function will return ``NaN`` for correlations of equal valued sequences; this is the result of a 0/0 division error. When `pairwise` is set to `False`, only matching columns between `self` and @@ -1446,7 +1446,7 @@ def _get_cov(X, Y): Examples -------- The below example shows a rolling calculation with a window size of - four matching the equivalent function call using `numpy.corrcoef`. + four matching the equivalent function call using :meth:`numpy.corrcoef`. >>> v1 = [3, 3, 3, 5, 8] >>> v2 = [3, 4, 4, 4, 8] diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index b175dd540a518f..f4bb53ba4f2188 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -1073,6 +1073,7 @@ def bar(self, subset=None, axis=0, color='#d65f5f', width=100, percent of the cell's width. align : {'left', 'zero',' mid'}, default 'left' How to align the bars with the cells. + - 'left' : the min value starts at the left of the cell. - 'zero' : a value of zero is located at the center of the cell. - 'mid' : the center of the cell is at (max-min)/2, or From 7343fd37f809feeb98844a34dc5854a08df2aa94 Mon Sep 17 00:00:00 2001 From: Thiviyan Thanapalasingam Date: Wed, 26 Sep 2018 20:41:11 +0100 Subject: [PATCH 37/85] DOC: Handle whitespace in pathnames (#20880) --- doc/make.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/make.py b/doc/make.py index d85747458148d5..cab5fa0ed4c52a 100755 --- a/doc/make.py +++ b/doc/make.py @@ -233,10 +233,10 @@ def _sphinx_build(self, kind): '-b{}'.format(kind), '-{}'.format( 'v' * self.verbosity) if self.verbosity else '', - '-d{}'.format(os.path.join(BUILD_PATH, 'doctrees')), + '-d"{}"'.format(os.path.join(BUILD_PATH, 'doctrees')), '-Dexclude_patterns={}'.format(self.exclude_patterns), - SOURCE_PATH, - os.path.join(BUILD_PATH, kind)) + '"{}"'.format(SOURCE_PATH), + '"{}"'.format(os.path.join(BUILD_PATH, kind))) def _open_browser(self): base_url = os.path.join('file://', DOC_PATH, 'build', 'html') From a85140102691ae675a942da6673edd5d95fa9b06 Mon Sep 17 00:00:00 2001 From: Eric Boxer Date: Thu, 27 Sep 2018 08:54:11 -0400 Subject: [PATCH 38/85] DOC iteritems docstring update and examples (#22658) --- pandas/core/frame.py | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index cc58674398b70e..0d0e186b15c544 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -779,14 +779,50 @@ def style(self): return Styler(self) def iteritems(self): - """ + r""" Iterator over (column name, Series) pairs. - See also + Iterates over the DataFrame columns, returning a tuple with the column name + and the content as a Series. + + Yields + ------ + label : object + The column names for the DataFrame being iterated over. + content : Series + The column entries belonging to each label, as a Series. + + See Also -------- - iterrows : Iterate over DataFrame rows as (index, Series) pairs. - itertuples : Iterate over DataFrame rows as namedtuples of the values. + DataFrame.iterrows : Iterate over DataFrame rows as (index, Series) pairs. + DataFrame.itertuples : Iterate over DataFrame rows as namedtuples of the values. + Examples + -------- + >>> df = pd.DataFrame({'species': ['bear', 'bear', 'marsupial'], + ... 'population': [1864, 22000, 80000]}, + ... index=['panda', 'polar', 'koala']) + >>> df + species population + panda bear 1864 + polar bear 22000 + koala marsupial 80000 + >>> for label, content in df.iteritems(): + ... print('label:', label) + ... print('content:', content, sep='\n') + ... + label: species + content: + panda bear + polar bear + koala marsupial + Name: species, dtype: object + label: population + content: + panda 1864 + polar 22000 + koala 80000 + Name: population, dtype: int64 """ if self.columns.is_unique and hasattr(self, '_item_cache'): for k in self.columns: From d115900f4f80d2f9016b41041bde1564980415b3 Mon Sep 17 00:00:00 2001 From: Jeff Reback Date: Thu, 27 Sep 2018 14:15:58 -0400 Subject: [PATCH 39/85] STYLE: lint --- pandas/core/frame.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 0d0e186b15c544..d5b273f37a3a2d 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -782,8 +782,8 @@ def iteritems(self): r""" Iterator over (column name, Series) pairs. - Iterates over the DataFrame columns, returning a tuple with the column name - and the content as a Series. + Iterates over the DataFrame columns, returning a tuple with + the column name and the content as a Series. Yields ------ @@ -794,8 +794,10 @@ def iteritems(self): See Also -------- - DataFrame.iterrows : Iterate over DataFrame rows as (index, Series) pairs. - DataFrame.itertuples : Iterate over DataFrame rows as namedtuples of the values. + DataFrame.iterrows : Iterate over DataFrame rows as + (index, Series) pairs. + DataFrame.itertuples : Iterate over DataFrame rows as namedtuples + of the values. Examples -------- From e45a6c14fc969c31c46ae890e72883ffe6658b4e Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 28 Sep 2018 10:06:15 -0500 Subject: [PATCH 40/85] COMPAT: mpl 3.0 (#22870) * COMPAT: mpl 3.0 * faster test --- doc/source/whatsnew/v0.24.0.txt | 2 ++ pandas/plotting/_compat.py | 1 + pandas/plotting/_core.py | 10 ++++++++-- pandas/tests/plotting/common.py | 1 + pandas/tests/plotting/test_datetimelike.py | 8 ++++++-- 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 481c31d2410a99..3e1711edb0f27c 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -193,6 +193,8 @@ Other Enhancements - :meth:`Series.resample` and :meth:`DataFrame.resample` have gained the :meth:`Resampler.quantile` (:issue:`15023`). - :meth:`Index.to_frame` now supports overriding column name(s) (:issue:`22580`). - New attribute :attr:`__git_version__` will return git commit sha of current build (:issue:`21295`). +- Compatibility with Matplotlib 3.0 (:issue:`22790`). + .. _whatsnew_0240.api_breaking: Backwards incompatible API changes diff --git a/pandas/plotting/_compat.py b/pandas/plotting/_compat.py index 46ebd4217862dd..5032b259e9831e 100644 --- a/pandas/plotting/_compat.py +++ b/pandas/plotting/_compat.py @@ -29,3 +29,4 @@ def inner(): _mpl_ge_2_0_1 = _mpl_version('2.0.1', operator.ge) _mpl_ge_2_1_0 = _mpl_version('2.1.0', operator.ge) _mpl_ge_2_2_0 = _mpl_version('2.2.0', operator.ge) +_mpl_ge_3_0_0 = _mpl_version('3.0.0', operator.ge) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 4fa3b51c60ee48..77c97412bd3d7e 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -32,7 +32,8 @@ from pandas.plotting._compat import (_mpl_ge_1_3_1, _mpl_ge_1_5_0, - _mpl_ge_2_0_0) + _mpl_ge_2_0_0, + _mpl_ge_3_0_0) from pandas.plotting._style import (plot_params, _get_standard_colors) from pandas.plotting._tools import (_subplots, _flatten, table, @@ -843,11 +844,16 @@ def _plot_colorbar(self, ax, **kwds): # For a more detailed description of the issue # see the following link: # https://github.com/ipython/ipython/issues/11215 - img = ax.collections[0] cbar = self.fig.colorbar(img, ax=ax, **kwds) + + if _mpl_ge_3_0_0(): + # The workaround below is no longer necessary. + return + points = ax.get_position().get_points() cbar_points = cbar.ax.get_position().get_points() + cbar.ax.set_position([cbar_points[0, 0], points[0, 1], cbar_points[1, 0] - cbar_points[0, 0], diff --git a/pandas/tests/plotting/common.py b/pandas/tests/plotting/common.py index 09687dd97bd43b..5c88926828fa6b 100644 --- a/pandas/tests/plotting/common.py +++ b/pandas/tests/plotting/common.py @@ -57,6 +57,7 @@ def setup_method(self, method): self.mpl_ge_2_0_0 = plotting._compat._mpl_ge_2_0_0() self.mpl_ge_2_0_1 = plotting._compat._mpl_ge_2_0_1() self.mpl_ge_2_2_0 = plotting._compat._mpl_ge_2_2_0() + self.mpl_ge_3_0_0 = plotting._compat._mpl_ge_3_0_0() if self.mpl_ge_1_4_0: self.bp_n_objects = 7 diff --git a/pandas/tests/plotting/test_datetimelike.py b/pandas/tests/plotting/test_datetimelike.py index 0abe82d138e5e5..de6f6b931987cd 100644 --- a/pandas/tests/plotting/test_datetimelike.py +++ b/pandas/tests/plotting/test_datetimelike.py @@ -151,7 +151,7 @@ def test_high_freq(self): freaks = ['ms', 'us'] for freq in freaks: _, ax = self.plt.subplots() - rng = date_range('1/1/2012', periods=100000, freq=freq) + rng = date_range('1/1/2012', periods=100, freq=freq) ser = Series(np.random.randn(len(rng)), rng) _check_plot_works(ser.plot, ax=ax) @@ -1492,7 +1492,11 @@ def test_matplotlib_scatter_datetime64(self): ax.scatter(x="time", y="y", data=df) fig.canvas.draw() label = ax.get_xticklabels()[0] - assert label.get_text() == '2017-12-12' + if self.mpl_ge_3_0_0: + expected = "2017-12-08" + else: + expected = "2017-12-12" + assert label.get_text() == expected def _check_plot_works(f, freq=None, series=None, *args, **kwargs): From f771ef67e8fa3f395967d5c02918fbbc3410612a Mon Sep 17 00:00:00 2001 From: gfyoung Date: Sat, 29 Sep 2018 12:55:51 -0700 Subject: [PATCH 41/85] BLD: Drop nonexistent dependency of _libs/parsers (#22883) Follow-up to gh-22469. Closes gh-22831. --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2aca048dcd4fb9..bfd0c50c9e9be3 100755 --- a/setup.py +++ b/setup.py @@ -544,8 +544,7 @@ def srcpath(name=None, suffix='.pyx', subdir='src'): '_libs.parsers': { 'pyxfile': '_libs/parsers', 'depends': ['pandas/_libs/src/parser/tokenizer.h', - 'pandas/_libs/src/parser/io.h', - 'pandas/_libs/src/numpy_helper.h'], + 'pandas/_libs/src/parser/io.h'], 'sources': ['pandas/_libs/src/parser/tokenizer.c', 'pandas/_libs/src/parser/io.c']}, '_libs.reduction': { From f849134a0af2a6a1cc016f61c68ba24d08b8a9a3 Mon Sep 17 00:00:00 2001 From: Marc Garcia Date: Sun, 30 Sep 2018 05:22:36 +0100 Subject: [PATCH 42/85] Updating `DataFrame.mode` docstring. (#22404) --- pandas/core/frame.py | 74 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index d5b273f37a3a2d..b4e8b4e3a6bec5 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -7303,38 +7303,82 @@ def _get_agg_axis(self, axis_num): def mode(self, axis=0, numeric_only=False, dropna=True): """ - Gets the mode(s) of each element along the axis selected. Adds a row - for each mode per label, fills in gaps with nan. + Get the mode(s) of each element along the selected axis. - Note that there could be multiple values returned for the selected - axis (when more than one item share the maximum frequency), which is - the reason why a dataframe is returned. If you want to impute missing - values with the mode in a dataframe ``df``, you can just do this: - ``df.fillna(df.mode().iloc[0])`` + The mode of a set of values is the value that appears most often. + It can be multiple values. Parameters ---------- axis : {0 or 'index', 1 or 'columns'}, default 0 + The axis to iterate over while searching for the mode: + * 0 or 'index' : get mode of each column * 1 or 'columns' : get mode of each row - numeric_only : boolean, default False - if True, only apply to numeric columns - dropna : boolean, default True + numeric_only : bool, default False + If True, only apply to numeric columns. + dropna : bool, default True Don't consider counts of NaN/NaT. .. versionadded:: 0.24.0 Returns ------- - modes : DataFrame (sorted) + DataFrame + The modes of each column or row. + + See Also + -------- + Series.mode : Return the highest frequency value in a Series. + Series.value_counts : Return the counts of values in a Series. Examples -------- - >>> df = pd.DataFrame({'A': [1, 2, 1, 2, 1, 2, 3]}) + >>> df = pd.DataFrame([('bird', 2, 2), + ... ('mammal', 4, np.nan), + ... ('arthropod', 8, 0), + ... ('bird', 2, np.nan)], + ... index=('falcon', 'horse', 'spider', 'ostrich'), + ... columns=('species', 'legs', 'wings')) + >>> df + species legs wings + falcon bird 2 2.0 + horse mammal 4 NaN + spider arthropod 8 0.0 + ostrich bird 2 NaN + + By default, missing values are not considered, and the mode of wings + are both 0 and 2. The second row of species and legs contains ``NaN``, + because they have only one mode, but the DataFrame has two rows. + >>> df.mode() - A - 0 1 - 1 2 + species legs wings + 0 bird 2.0 0.0 + 1 NaN NaN 2.0 + + Setting ``dropna=False`` ``NaN`` values are considered and they can be + the mode (like for wings). + + >>> df.mode(dropna=False) + species legs wings + 0 bird 2 NaN + + Setting ``numeric_only=True``, only the mode of numeric columns is + computed, and columns of other types are ignored. + + >>> df.mode(numeric_only=True) + legs wings + 0 2.0 0.0 + 1 NaN 2.0 + + To compute the mode over columns and not rows, use the axis parameter: + + >>> df.mode(axis='columns', numeric_only=True) + 0 1 + falcon 2.0 NaN + horse 4.0 NaN + spider 0.0 8.0 + ostrich 2.0 NaN """ data = self if not numeric_only else self._get_numeric_data() From 14598c6f82973b33d8b55e1e493f6103f9e16a62 Mon Sep 17 00:00:00 2001 From: MatanCohe <30339529+MatanCohe@users.noreply.github.com> Date: Sun, 30 Sep 2018 09:48:40 +0300 Subject: [PATCH 43/85] STYLE: Fix linting of benchmarks (#22886) Fixed the following: * asv_bench/benchmarks/algorithms.py:12:5: E722 do not use bare except' * asv_bench/benchmarks/timeseries.py:1:1: F401 'warnings' imported but unused * asv_bench/benchmarks/stat_ops.py:21:9: E722 do not use bare except' * asv_bench/benchmarks/stat_ops.py:59:9: E722 do not use bare except' * asv_bench/benchmarks/pandas_vb_common.py:5:1: F401 'pandas.Panel' imported but unused * asv_bench/benchmarks/pandas_vb_common.py:12:5: E722 do not use bare except' * asv_bench/benchmarks/pandas_vb_common.py:37:9: E722 do not use bare except' * asv_bench/benchmarks/join_merge.py:32:9: E722 do not use bare except' * asv_bench/benchmarks/io/csv.py:2:1: F401 'timeit' imported but unused * asv_bench/benchmarks/io/csv.py:8:1: F401 'pandas.compat.PY2' imported but unused * asv_bench/benchmarks/io/csv.py:184:80: E501 line too long (87 > 79 characters) --- asv_bench/benchmarks/algorithms.py | 2 +- asv_bench/benchmarks/io/csv.py | 6 ++---- asv_bench/benchmarks/join_merge.py | 2 +- asv_bench/benchmarks/pandas_vb_common.py | 5 ++--- asv_bench/benchmarks/stat_ops.py | 4 ++-- asv_bench/benchmarks/timeseries.py | 1 - 6 files changed, 8 insertions(+), 12 deletions(-) diff --git a/asv_bench/benchmarks/algorithms.py b/asv_bench/benchmarks/algorithms.py index cccd38ef112519..fc34440ece2edc 100644 --- a/asv_bench/benchmarks/algorithms.py +++ b/asv_bench/benchmarks/algorithms.py @@ -9,7 +9,7 @@ try: hashing = import_module(imp) break - except: + except (ImportError, TypeError, ValueError): pass from .pandas_vb_common import setup # noqa diff --git a/asv_bench/benchmarks/io/csv.py b/asv_bench/benchmarks/io/csv.py index 2d4bdc7ae812a0..12cb893462b878 100644 --- a/asv_bench/benchmarks/io/csv.py +++ b/asv_bench/benchmarks/io/csv.py @@ -1,11 +1,9 @@ import random -import timeit import string import numpy as np import pandas.util.testing as tm from pandas import DataFrame, Categorical, date_range, read_csv -from pandas.compat import PY2 from pandas.compat import cStringIO as StringIO from ..pandas_vb_common import setup, BaseIO # noqa @@ -181,8 +179,8 @@ def time_read_csv(self, sep, decimal, float_precision): names=list('abc'), float_precision=float_precision) def time_read_csv_python_engine(self, sep, decimal, float_precision): - read_csv(self.data(self.StringIO_input), sep=sep, header=None, engine='python', - float_precision=None, names=list('abc')) + read_csv(self.data(self.StringIO_input), sep=sep, header=None, + engine='python', float_precision=None, names=list('abc')) class ReadCSVCategorical(BaseIO): diff --git a/asv_bench/benchmarks/join_merge.py b/asv_bench/benchmarks/join_merge.py index de0a3b33da1474..7487a0d8489b7b 100644 --- a/asv_bench/benchmarks/join_merge.py +++ b/asv_bench/benchmarks/join_merge.py @@ -29,7 +29,7 @@ def setup(self): try: with warnings.catch_warnings(record=True): self.mdf1.consolidate(inplace=True) - except: + except (AttributeError, TypeError): pass self.mdf2 = self.mdf1.copy() self.mdf2.index = self.df2.index diff --git a/asv_bench/benchmarks/pandas_vb_common.py b/asv_bench/benchmarks/pandas_vb_common.py index e255cd94f265bf..e7b25d567e03b9 100644 --- a/asv_bench/benchmarks/pandas_vb_common.py +++ b/asv_bench/benchmarks/pandas_vb_common.py @@ -2,14 +2,13 @@ from importlib import import_module import numpy as np -from pandas import Panel # Compatibility import for lib for imp in ['pandas._libs.lib', 'pandas.lib']: try: lib = import_module(imp) break - except: + except (ImportError, TypeError, ValueError): pass numeric_dtypes = [np.int64, np.int32, np.uint32, np.uint64, np.float32, @@ -34,7 +33,7 @@ def remove(self, f): """Remove created files""" try: os.remove(f) - except: + except OSError: # On Windows, attempting to remove a file that is in use # causes an exception to be raised pass diff --git a/asv_bench/benchmarks/stat_ops.py b/asv_bench/benchmarks/stat_ops.py index c447c78d0d0703..ecfcb27806f543 100644 --- a/asv_bench/benchmarks/stat_ops.py +++ b/asv_bench/benchmarks/stat_ops.py @@ -18,7 +18,7 @@ def setup(self, op, dtype, axis, use_bottleneck): df = pd.DataFrame(np.random.randn(100000, 4)).astype(dtype) try: pd.options.compute.use_bottleneck = use_bottleneck - except: + except TypeError: from pandas.core import nanops nanops._USE_BOTTLENECK = use_bottleneck self.df_func = getattr(df, op) @@ -56,7 +56,7 @@ def setup(self, op, dtype, use_bottleneck): s = pd.Series(np.random.randn(100000)).astype(dtype) try: pd.options.compute.use_bottleneck = use_bottleneck - except: + except TypeError: from pandas.core import nanops nanops._USE_BOTTLENECK = use_bottleneck self.s_func = getattr(s, op) diff --git a/asv_bench/benchmarks/timeseries.py b/asv_bench/benchmarks/timeseries.py index 2c98cc16595199..2557ba7672a0ed 100644 --- a/asv_bench/benchmarks/timeseries.py +++ b/asv_bench/benchmarks/timeseries.py @@ -1,4 +1,3 @@ -import warnings from datetime import timedelta import numpy as np From f4fae353eaaa719db335ec2b21259932de30f46d Mon Sep 17 00:00:00 2001 From: Ming Li <14131823+minggli@users.noreply.github.com> Date: Sun, 30 Sep 2018 14:59:46 +0100 Subject: [PATCH 44/85] BLD: minor break ci/requirements-optional-pip.txt (#22889) --- ci/requirements-optional-pip.txt | 4 ++-- scripts/convert_deps.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ci/requirements-optional-pip.txt b/ci/requirements-optional-pip.txt index 2e1bf0ca22bcf0..09ce8e59a3b46e 100644 --- a/ci/requirements-optional-pip.txt +++ b/ci/requirements-optional-pip.txt @@ -14,7 +14,7 @@ lxml matplotlib nbsphinx numexpr -openpyxl=2.5.5 +openpyxl==2.5.5 pyarrow pymysql tables @@ -28,4 +28,4 @@ statsmodels xarray xlrd xlsxwriter -xlwt +xlwt \ No newline at end of file diff --git a/scripts/convert_deps.py b/scripts/convert_deps.py index aabeb24a0c3c86..3ff157e0a0d7ba 100755 --- a/scripts/convert_deps.py +++ b/scripts/convert_deps.py @@ -1,6 +1,7 @@ """ Convert the conda environment.yaml to a pip requirements.txt """ +import re import yaml exclude = {'python=3'} @@ -15,6 +16,7 @@ required = dev['dependencies'] required = [rename.get(dep, dep) for dep in required if dep not in exclude] optional = [rename.get(dep, dep) for dep in optional if dep not in exclude] +optional = [re.sub("(?<=[^<>])=", '==', dep) for dep in optional] with open("ci/requirements_dev.txt", 'wt') as f: From 2f1b842119bc4d5242b587b62bde71d8f7ef19f8 Mon Sep 17 00:00:00 2001 From: Kang Yoosam Date: Mon, 1 Oct 2018 06:27:17 +0900 Subject: [PATCH 45/85] DOC: Fixes to the Japanese version of the cheatsheet (#22657) --- doc/cheatsheet/Pandas_Cheat_Sheet_JA.pdf | Bin 0 -> 206153 bytes doc/cheatsheet/Pandas_Cheat_Sheet_JA.pptx | Bin 0 -> 76495 bytes doc/cheatsheet/Pandas_Cheat_Sheet_JP.pdf | Bin 205542 -> 0 bytes doc/cheatsheet/Pandas_Cheat_Sheet_JP.pptx | Bin 105265 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/cheatsheet/Pandas_Cheat_Sheet_JA.pdf create mode 100644 doc/cheatsheet/Pandas_Cheat_Sheet_JA.pptx delete mode 100644 doc/cheatsheet/Pandas_Cheat_Sheet_JP.pdf delete mode 100644 doc/cheatsheet/Pandas_Cheat_Sheet_JP.pptx diff --git a/doc/cheatsheet/Pandas_Cheat_Sheet_JA.pdf b/doc/cheatsheet/Pandas_Cheat_Sheet_JA.pdf new file mode 100644 index 0000000000000000000000000000000000000000..daa65a944e68acfc9ce5e4b47929d806ae6ba41b GIT binary patch literal 206153 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�ci(J|R{ zzN`l9jKy)C97Pf_e}>*gJ?a|Rn*lf&kaV5qz7t4!ln-=>`uKaNxubp)TSG?)`(V59WjrBAY z1Ll(?7#~_|qXwJ@PlHS|H78#rKCLYd;Twe7TpiH&P3=%}UaK?D2yj1SE_MU5z2AmO zAR^PF`0;q2ssF=CB)sCt(MR#tGcLySMSgkn*<$|H67=J*lQJ2zX0$MG84?f7|UpF*xp z@X{=!6KbGUe{U<5oLdkT7NoB{NXmr_Z5&-N-J|T~ePJ`57cA|9Sc)cCc%YA+%V=R+ z2whgo3soo8szLV2t{rz+R8woLbeQ&j8FD@R=ZL?gO`e&dm(S%him)8t)T5Cs6Z+oP z7wuWq*|jNO;ztI4?Z&TJJ_nI7q=({N(^xeAm$C1l%d%(Q`P9Ny@KgO4ei!y%zmHU4 zC_~~8v~*uXS=+>+N29_))3`geCe0`>PyV*4t){PiW~i>;@D+{A*>xxe?1)j2Ck|62 zbkQ;JH`IOq6wEomGy|A<hY$s^R->Z;4#kTK=wfq zP?Xh4`s~uAZw!NF{pBj(qHJXh?F890GX#DTSN4Ym-s1>FXIQn6;1VZNK`O>>VsJ5vN3V|5h$2jefHvna()4mQKVXN8kxJk?%cTC8qV1FZ@snx$i zY{|@Lb9ixKjrGo&V=LVMeeUY5M*IgFT8yegc_(sZ zbXtB+frJ+wsxLn!D1*TU>VU$&4saQc+DhxH1?Gmb(PI37x(?hg6|_P|q{-|B6y};S z2+`M}0UYghhrKex_yLs!c06z$1#_3zQb8&q`3ly0h$H(N^)?bwow8PCpX53@)R@|; zf#8s2Az2{Zy%_uk>kHvlK$E<|#=sMI>m7Wq0#q_0!W7yYNv^yS4co{cf%ufVLGBlj ztUwlw!q^0&($k|Lxy%13Q>UEZ&b2dm5dxbR(ejJzdFK4Z=KkJqyd0dju`cU;&8V^=8^4poc;)};JI1PI$gAChg{HulWyA^lN>yodKGr70f zzi$%_dy6?=zOd4H^cso8uW%k^a53rw?Ar`Ue1*bTGF*yrh>M_V<8LJ4B5@#r+XTFq zFUUnnXs^DjlfdZL3HmMQfd z9tR<_^D-`D{6AQ~lcjwu!1-6m@tSMj-k$!M{wqrM6Nf~H9zX08@|K@1r8KWf)?+sr zY^Khpsi~&U&VGAe{UQwn)(3e9vwi8KnR#czc+rvCQh!0ERG$h|kg~Z|Y2#hCmv=xo zQo9MGQv9%{2ew!@!1hOpMAr8uvNn|(s+(wNww`Mu*8<8%nGnLNmbak*P+Pl1EVU#K zXC8%a+Ody)_-J*OA-yu?`q`20fiaqG!`GNl5N4B*1jvDN)PtX{kL^aABXbHP&X{*| z%JdD!>XKQ#$;NqTN&jm}q z^J~}yxEp+{R)NB#vV#Nrad2Q;Ku-xp2^qOY6a?4-@+XAqOh9f=kRqHw5^Dn#;UZkb z;IDVEU!zm2@Ymbm(G8bRI-dpz88P>u(~2)J!(b;R=7+rq5`J=GxTn}o%182VTL96z z<81WibL@Y-&%Wvn8A++-l5cuH?wcDJ?doWsxj-vpRH8&KsGt2+DFr0(qrjo6hWgx% zvZ?BoUHnncdhuk)%_AR$;1z*}`Z8mUm8H73b-!^Tq&dMBan@Big1W2)q#gxAF&GS9 zb^0>IF#rp>1hsS|Q;CmZL@f9)HyHddXppA)yHcBlOmc1woc6U!VW71cKOT|4_w8~f<| z#d9Nl*FT|)OEqe}o?d<`jlrYnm}gk*kuZY_63$tjK`MUowoK_|TTe6kP7OJH{xo>r&{W*_3R zcjMWoUS%KV+cPV2Ey-;IwOzI2?cZ52PmR~oMX(8)fs5BK??b1c3QBN^Dpd9nu5(`} z)eGE$ooFhgiJ(wh9>9rAJ=F?9lNKUDhs_{jx#Ab8AeT#gCA5a&(tIZ{*aTy>U7)J)!ccK!4ZrrD}S(>y=t`7_= z2)oe}+pWIN42q_IU>%b>nbjm3@k!>?`y1kFynrH{b8<$%AN>(HgVY7*23R@MmwdYz zX>LJiV5qjlgI}JYOH+ekZ>mq#%KOSz1%VR%RA*bAt)_M!G+`Qzp{BC3r1)e%CpjWA z@W25Y8=%-Fat%U;LlGZ~Om>5{u5YiAlOCFSIw_uh4TS-6N+jlVSl*W1sKTRuL`{@m zP?W^Et_S_#RrTa0**1wU5HZUq>(V*?xLQIlqnS4gq!GuZlJC5T`+bpBO{)Q8>o z*iJEo<~cVUrj|G05XQOTjpYsSBYu`y-tePy19D57UfMOi^4s%E`#^y6=2A6&idldD zNcX<3D<92%e)i_j_n@JzI@rR=6X!}ZM5kUl^7i)k-#d4xkzF+q?aTQ`#B{=4|Lyxd zBA*ScNjGFvN%6|*#l9O?@17Wm`EozaehQk&sLztxPh2=D=1lH3pKjYiW6@_!+%0*h z|HTs$&ftDquwxs&Of3&GvrGFtpl)kWH7szK9-_u!-s=8$Al9G%e}P!{9;6PdL@+g6 zUFv6+_TAfcZy(G;#&KLV<_9;FW7@s*zq}wluK*|_n9rh-p}VBPFl-nG#fWil6{ql! z{C&}m=+~nTpEzR8D$g?|x1Ozb)D2nK#~Lle7W{=$wv1cY)bzyMXXjfNW(E># zvIk;lAJB!RdZh`YgCk7xlH(S8cX?HH1MTkdiSt1QbAeH*0ecf9KuOgp30$C^pKzs3 zB@q_YLW0=7N6GQ|`59?pL6M?eA+8p8($81Bdq(fl3Xv*X&h?@)Ah#`(4~awu61g)) znN*dE27bGzLo^#?X;!Ois*g8?Hw!!IpHUkmewcu@Q>3QD_gqH!Ev7Zl6l*yS3JB>~ zN^{IGjW25%*)dr}xZj9`)821tZiAhb<7aR+F$1l{AD(xvga(BZTqt7<7*r@K1Q8ZcTMblj0ML0P%~Idm4hNlYpk9dZY;^?%#uE6j6<1>(ItPW!eM63rsB`~_ z!=b6#0;aS!*Muk}3g9I)tm-?>QOLnmelJ_dVA%RTB|MDW8@Imcz&#u zrK2_;-P8dJ_u9;JS4>?Euw&5!>?x-4djo{=uJ9@26ugkRJMK*gegU^AVCeQf?vf|f z4Q`fJl0h1yiI3#oI}FX0P;kazPv;#bYbI?Xr8gjjQ|DC{Q+AMci@G`vRu31&1^4Oz z>_Cvc;{;0|-4OYs4F5+SewM~jXdDRZW}<(SB&eVzBt*)=qAC-&3y3z?w)T+vu45mg z*&%c?inP$6EuFRx3_#f|Pqv<);Uy1=JYmT)W|@+D=S_Vz*GxA`Zxlmu1M)J}`vu$c zLg=8pGr6Z>=PF&Lx1cmpldegi^ZM(Q9lR-PS#y=Z*jWBiG3-e69VAp(01T%RtGgFc zpH;Wr<>KE^IlHBieP-aP&FkJg#6Iq8gKb@^6EQV$Yv%L2j)3~eCLtZ(-6M7I9JSRg z)q<+V(t5+i6c8vUS?D*iQctAua?{F;DT2T*NlV<~^vC9oY3Pvjlv}lWIui_72gYG9 z;I80k&ZTfm+SEzfEqv^ov72K!pzn+R(s9+u>Yv7bnhS;|^=R4*v`*Hn(7S)5|F7*! z(F}lOB~-AG3DaF_HufDcaPk6E{Sth|C&ker5!STQGnHwMMng?qoApF}NO#~7Z?Cw` zQkO0(k-pnJ-n=+Fb9_4LPBciTD{8FoIlP-ZM@HuzqmH|DR~3npVFh|dz9%UpZ?4T)zKueGqLK+96*xPd>rKg2GG#Jf zYpI4`&mq5KyZzsbHKmv48fD+~Uz+L#TUER&Eu9+$j9-Q#oj;EhwJM{7+fG%gEXo=_ zZ0V~i=Jy`&Jbfv=(pFqjU46(AS|8R&Uqk~&m9k2pG?8ezNoe0zXlnQhX)3Ij>*w$j z@R2=Q0pD6l1`Xg_C}<9_Z!qt&=nLphKjF>|#t**>U%)MC9)B^1yI?Qs#LGAkcI9$9 zb9xvEV*UjU!wua6yC1*}7Yl^fFTT`qh;xf`#ksYC%zsIM23->d#JNSsf$@s1!e2nh z<^V9pcH+a7!~>9L{csmfM|}V?TVboK!qc&!YhL~W`NQRk6YKz=Ya3X!a0ha`HMDqc z`rP!;=&*dUVPJM>H9k3uU!K9w%s>Nj4SU7?kzep{&Q$Pc)5S|3@*oc81DoZk3&4P{*w%LLK1t%h2LiA}{pt)TO*Ad1Jgo4BZc>4W1f`=cX zCEz+f?*XP@5-2DYfQR4evJF3S5ERuUi9s&tpxl4^MUhPCkXY!(HJ3M3*ef*ZQk_vB zImXG(%`HgE-50k*{(4rmOn0VOHU;5beUNna(s7Ce`AA3y1t4M%ZwSI2X)l)E`JOo$ z6z@s%pLBiR1l4rmapgf!&dDHmG6r}Zmm#1Tlw`$JuW~^##JBG<#dMyks3m09@oC|( zMp||Ipf)x`Cqj8Yif9^zCaN`30pzQvo&=2z6{?C; zB=alPP3j>vEy5QWymX9RLP4wk_wf+?JnzBmJb6O;iqs<2=T}O9X`XOghs{c!RWYCV zOeFjeXRw!6R-4|iAGdBDq}`In@atn=jmJ;UjfYNLpZR>!XJYl=XYht;d~a^e4|mo) zzQUdR%hFHImqC!7LNHzn61qs(1C&Ku3A+`IvUEf6l|4H>KMZ>=8WcELm6Cs*y*Y4s z_Qv6hQTP#mXn<8!+aON4eRun=$@#^RYaO2&>cL@9PQNc>!rzPW^Yq+zKE5yNHobhx z$phoBjd%lis0EC_t}-*x)q&@sXi!Zi3SvlT$o;O>so#S|2qI4-vxL|0!6NPCHTPY= zPorj7rRxag58>c2Yd=k}2aFB(Z^a~A<}hyxKLr}idFEeLH`8U?F%qYoc{N?Z3Umjw-juupzbRHn{iHA z+(;cK;YQLx_@xjT#}ZzWWr#m=CQ1>hkg7O?bJL%9FP^&wrI^XHos$_R2|VyZN?mTH zsHJ7lan9CX(^6VhQgNh_lN%DXJ$S#|qSDB0(f*uS&#r(@pF{}m4md)q((;Njco+8Dbkdkf%pr=Du|f)@ zke&dc_g)M(xZB{~d+)|ovYM-G$(D=U<=%U*bOIzm!h{rN(!(S(v*q_VzqxA*=6&z| zeD3G{OB#i-W$B!~_wzh!eHTRhxg~{=5$Ed;>V;?BLan?`cv-nQA9pNEC{8X+s4CCR zEk`nHTuxk~Xz_%=wJ_G0Y?)d=nTa&g4c+-o28q6=us83o;rYpVp?c|_Os|wMHZUnD zgMUf9$w`?PEs4p^%&)*)4mzYRvn>VPCLOHTtMPr5p^=as6G{a?y(>eZGK`h9ch`5< z4YOFa!cK*I`eGWr8hTuak$EvgtO-C`FXH)&%pw-TRQ~y--2q!B!iDvBwO2LM4pzP}xZ{@(ZLFeDN4a}z&JXH8z7 zOr8F1a>eOt3-4DoB_W~ z`+xamYnXsIIf8A`cub8F?G#w(ZlBVolc&h4Jk?Jc%UE?nUbH6LBP%97jZI4n&)zEz zj8-VmN>Wu)l}_~k`i&(yT7ziuwZ%xz#@;pbWB)2>Up29};f%gK%aWzp+!gC7zi)g? zD)I{}%0H;Hsog)s{_zZu$>)yN`HgNH=@Z&r3T)bckv+V)hf`IR$&KPUkb({!cJ(n& zJ4+A%UZ!oSM{Yx5Re5P?Lt}$WQ$QD;d5)TAo==md3iWT2ykz*9~JXY zSmjsROWgEP72zW+u@{Wylv808m{|d0r-!z*$pB!_K#RDR~4x^br9cKsInl zXAa;&>b4>ufOaiUz~*-v*c!70lkG&59TY!+Y>&Eh*1WmhGpJ(%>>f&;VM)H!(%%j4UoAg(*KDY(w}=rLQL z_uVSu@>4XCvyF}A-Nx%v^Vj+j3~c)3^7>$whm31(nIh*?^@hRN+xjrW7JqJ`@P4bBJ!z==nt?intY&N$UP)Sxv` zB1{XD<`v@*?d!Jtl=FM8W$`8Pg|WAK``bGwjJ;L;Wm7%;lYf1aSZpq;E-Wc6W4Cvn z>~)y+?TK!Qm}lEa3^(Ld9^Pcr-z*I332t)fWB+*Vf0lBe+-w|g|Ce>GF#c3Zc(_NH zTTJBXGjM}MacSSD4MpBPepaa8QgBrHK43GoT;yM)CcSQ3CkR`&m0OjrXUQD{=lFGC z-;Qq_{Dl{`7##-QV3+#=LoiL&LYn3ZY1&GNUc=A_PNl zy{(`G9q6%e$pg}bU$DlsnseQC^62hkyRF_j@sU?mctMIL?5CkSGZbZGR8(Ho=qQ>eM?EAl$Lj5Mr^k3@CJ$+qr6o2U!o-;5#ORpgr{a_By}fGE)n10duGW_34wn2x z-4JFaSSF4LIL+Vu0yFhyFi;w3aRfUujtx0Ch>^kI37--gF(LkR-dm@pBx`lm12_x< z+%oxGN`5wjW$0&we4@e~fHrzVdKN_Ms>WIhaEsf>Pc!zAj!@{-1LGknWkUU8<6OPH zs}l2FtKD6Nx7_DEuh}N0Wo9RyWo2%>CD1M*p$e2(BzXhp;~5=yI_OMrP@;8|A+{(* z8}(!F`Ti?6SW;fZ-Q0G?{wMq53S6bSBQ-%_J^dX$-7VehrM2e{ z{l2-_P*hT0$uGTi;KH$Q{z}m`?=+CjxoxWIyk;cnP=Q&|G42d+@6JEE|7B~k6uG)l z8o2?9yitMrg!hU=8n~UTx0|&|jnoh9G z4^0uj(VGLm_xDb^!d^4cVv3DYjj0apn5=@fW^z1yrAY^FKa1HUWay;2!`>rmj5m_%+N?^p>;0 zV%h%$LF^NWZnwqb|D!VKdOE~kFfz2pRqKM2%_YsX~Ir72F{0Gc(I4*Y~hjf%YKT4hh zKy-eZ?4167?sv+rP5EM>ggfWj;nRDvw_oTo>Sa9G&u#)<>%2#&|HO&TQQ^s9_l5&q zEP0%7=K@|2KkmLW#wRH@Hg?Kh2sM6wan-<`=G(3RW=Y9@&WMkxQ6jC98M{TMErLF9 zNa`%5h!%s2;022Jr5{JQJite^xLshC69?TO6|c64BTY!f*$G_tPO5|6iGZDlPhP|m zxKWH6xUEKZGy0j;=?+3Yw}Sfhr^r(-Hn! z4Ck{c*lX_ziYqURx^&*y+woV{lm!UIl)?zv9Igj%g}=S%k9PzS8QCtuzDl*HC1ANp zIEhHB1`rtaRfTp=vi$HgkxvqwkOU7RS?*&CN?3TGWZ{j|0Hp^V(+7&$UA@duU1f2N zzP7xtt*opZY>T48Gu`0Pn(;#Y)DH$BGUTF-HB?@5fw6TAjI;`1T^S@=$3%;K0m64d z1J}Ig)Kl6GvIT=Ff7d?{j#p>%8&n0cnZ1o*tbJCdotN-mEr>av1EFWU?4h5RN6NGO zMXnA`r;j<&PD63%#872C-A^1Hq{<7B6ox4iR2Ui6 zfj%ZaU#kDIP?9%OK;Bb)&0paSBp#n z0pAUl7v=X+V>0MU?E^IGr}=!vVC6+kv$7z!WKSi|b@3;o>|FR|$KLti?XnnURz<+j zbg{nYI-6}LL(_aY`DK)x8(l)Knr?6t{vE;1f&Al}`A6+|p-*X4QCwaWVLR@$-5dLD z{@2fMe^pVLUoiKXFR(4)!Bk^(g3*fhbR2e%lH4G#((z6cmNe5dor${z~IpIPX_(eObtCpc85 zGUf3{xWxkG!)3YnIjQm-EsMfc$+-cnw6|K;C%#v%y(lqh40HsHBuY=YFI1JQ0_4YS*OWQC>HflpT)KZ|RLU7P9NucVB21!4l&7VM zVg+)~WyrYlmxW3rEEUE|j2~&l9QNmCinP*WvFk-~sRhzXilPtgYM7QMFf#6Bq3vnH zDZ8GE_k&~TDdGf^+oUMTA42>-a9JQtkTa z;p2OFTzn3fovnE(x<;3x7D!D81WnhAYAY^+?NE@Xhr=<$`K*7$VK@FUhmW^?s7)w| zDvIfy(l^#mvDvn=SUVAk1R7)t0dqYN2{+M_C?ph$oy7o?4b!V=#c&?D>tKa1lvzgv z$7DfSU@QfW%?_OFo9&zGpB|Y(t|KVVv!~|&Ig>j{4ownnRzADpKiUr#FPS< zK}L`)JtKnUuQ4vD0s`sWwy73ECM0jCg-g>@L)asP;QO}+klIpnIe_L1JOp};~7nJjy$i5CDRY>1*lj8X#xvK?|D#rSV}COgtnQB^0OLMM5UBNxujNcTPb}_aT$Y-49((wB7>^eJ+0Jixkt1J1u16L5 z=2a*ZP>}BgUS{~u<=%xi%qnE@r{f>G-_~P zgeoEtP*?-!043I6yG&YOyf`Th#e5WmOrzO`o(R7NhB5#@Hi<|LCPp-4{Gk%H{~NF} z>#&+_qW;HlOnX)@px8ye0jyc07xv1iU@GR?bsMj>nUOMd4WP2?#a%Z|Zsy*iS$kw2!7grP>`}bq z6AzNdpA_-)TLd(PG@Pns%%O3&Aj0OEuym=H6c9eWx=>l7B924f)O^<>ofGZ}v59*N z>Z1now4iH4@9|3~zWbOgu_kZ17S$D$YAboB`nB$NXGe@ckWA5V0EM!+2s#I~RjV-A z?ZB}kZU@GLT71v5%OhvL9RF_go6)bwzQw}$+(oi{#`Mw*wZf2th*>%vLSv0koFUc- z6~P#j=9>|d;?0IKFd`~}5et+1VMl;KPv%Q6*QQ!IFk*wuBov#?G|R&{Dk2qV8yP7l zCLmA->!=%}X;TyyL6J3NrWKln`Tl0SK`Wy!mjj5A+n~r-7Vpgy%EGdO)3GS9yd!6N z=|h~%Amn`MgNKiAnWTXN`3b>UX6!ybjb4mZ%^Mu7|1DL$6h`xjnddaCLEgG(ZS zj?d8I{}B|?aoNA?)PR>Cl`Lf?|zt)$@q`*f9rev4pRMbPyV+>>N`&oslFFL&bic2A5`sQj5UOg z=NYuqYC}N>=KRHbN`yICIoa8w6Cta8_gMPw_CK@tU|mdJdTG?Bw_C@$ei&Qz^D**n zB%3}`E+emrYx8mxUrSW=YF%+Po4$T;_D1n;M`gaNgoKfkg031_rdgc<>?b-_C&_xU zi|NIhZ4R6Yp>U|h_s!N?vmn2rhOV!B7>hp=fOqE&j62{&2zrX4GbO9zPA{y~(c)i=acD$2}pu*f@zj}R?CFoS%sx5gk$_-ypzq}Rxo z2*0tKqcYRK%#jynS8Tds5pm!2uNLl5m0Q#4bNLmy<$0BPwaWZl-9a68xa8AO_Ws-b z9FJL5rWB{@;)X_B#*CMm+^Qn#qgj{pe~%^G#2SQ#-;yY+RnT~|nN}w;Zng(f*iC}G zkLLvsJbhT-Bh7Zi=ZOFda~nWNG&CqHECi;UMewjN@rr~!#xXQ1)gvA_(mp{0EF)!f zrHx8Pd&~7Zrr&%nF;|Wu&Z2iu2p&WXL;GK6Ktpbt_hNPihMe_CFWWCH&h0F{T*vYq z7!ll$Ov?yDzAy{{P(8;hBG~L!TD*K#sRNz<3qeO|MPWw;YlHcRUJlUheg>d|m0V|& zOj;$$Zj)6Yux}mim@4`~BNGKD>1xIuZy2DEaJfHlWUnHDB}f`%si>|)M<|$}VgB`c z{%gAZf4vTm%WdY`CrS1M37N5;c=p~rv7K8HWD!JmEqu%cF>yhZ^M-{f0w81+vMs$( z4`2F**dDrw@NH=`=uElz^)(c^86=b1NDeak-r_d>)b3*8*;C19BCXlexEF2taNd@p zG;R58Jh3QRa4_ir{$3;7o4F~S7bU$J=gj&iC!X~Zd%LJLMx8>w3%h>GbV0s+NWV9UxRVAHy6{;d-E@(T_$W*UHPp?-U)*U@q zno<^595;HYzQ6ejSh;i<-d)g0A@=vkLoo8kzn)-g|YnV(K_-5z_ z(8rFwmI{UNrs9^f9;HP2geXD#JM&A0%3feLX@NWjTdtE~Xag zL6{ZkatgXcW7s3jX_V;Wd5e8M{mdMQR7=GL00eo6w=u2qLVR7v=#VSfSG1e)!gN0b zk^yBcB}f#P0AN0f5&!Fgm<+h1y_Kp!dby=4Ls?n90YILw zOEqv-{|oC?EnTl*?6RZ5O)bUs#f|I$g>*dV=7t1OfmaL8kZ0=?o8yfv@ne#G68#f` z**_jd$kn-HZKo$TV{JWg!>@hM1=b>a@#x|a453Q#hn<7v?)dSOZZ6bvr&KBxaPn&k z^5pWFeqG7n5R7+$Fy4hsPoO5y{MY=u9PQ4SF-!2cVtsXfjqhZf&0jCNI&hA3i%s#bFUB#a@mdh=^fWZ$ z-FMaL$faEzbs{AzwY13U#J?CyW+a$Nu!09&c`Rc=0d$3De>rahQ_uc3OODTeIYXLfR@}b7;?+kiZr{J?&K0YRRmDoe z)RJXQgCj! zZw=F>_dya16z^DQaVhR*93Z(51_bR74i3z0Kfsu3Y1iG7X@<+v>^Ii;(d^+aNTdLy_G4!9mZz z+32f_OLQgg^t(15J`ekzl=JhAP4EtL3kgfIiL8#wPt!%+>+3gudX2?`PM#dSGa=Q7O1&QjjG) z#4pc-@id742Nkqt#e`Aa#Z$ML>B51+_M+AT#EdIlM}+%)c`JYZqwLa9)mc^SXSr?4 zYk>d8FWt$nxGo?^R}$e)_|A?yb6fZow;sH(1us}+jG5Mu^PIdnr&<09ODifpDk^n| zJZ`thD<=rtUq9wFhIMA;YaLD0sbS9Yry}_$oL>((5bSy4cx7@G47gwZ+I_2$u$6Z- z-{+B6sYR5JOysR2Ke7x=Pf7O@2M4JN?Id{-&`d?LnLVx9wc?4|;?@#L?S*2f0?35o z9AMRzY<36*DK0++&DsR9nSgJBJTkrHw~ItFM|AT|0p-$1y?d*(^`T)5?OwmiZB?7$pu6L%rBD|52`)l`L%huOdDo5TEMe9~9~Wd?jwF zP%jaIgbhn`1*Q|GCz&<~`zvX`3*QgQF_v)y3^qz)a^~YXBO-dr(13`gLnbv73KqY4 zBoP@U;?o5lVD*GpUigOSnJPU^(UX?alRSTmVCUB%+eBxk&4iBM8NW9C`N*f(VmF;9 zAIy@@dFkAWq?8p)wp%QwEJTt8?qmKBfje$bGcBOr99s7WZ_h-hXL*LWsq!5VIIB!V z09|%_qpVT(L|IZFTWrNrerx*4EZ{ zvQigWfV*h9&9vh*SvB_a*tIDi)LoCyk*l{ffT{<>QwSF;X2e6rIw}T1?_oXM&`yr z>yq_;W@vmWdp0pp`mT6win2~}Pw;!=XN}jpI??>q<-3jvtsGa`tqEoqS1uibSiXWb zhoa;$SbdZNzx)J4s%3I*L-vmlo;Ry2DkX-hqK^C@qH-e(;|jBPrJqhoWaE?kGd7Fq zJT_7irb^EP%HT=C_44|{X+w+lW=C(e)2MKd&6}>TgtChnyff#F30 zxAHD96ueX!Tac;^|B*a;o;=$}Ha6_(I5K*4`?kZbub-}rqBz>0M=lJ0a)Z6G{k-F^ zCkv|zOUezah7R}axibAp@8>P|^Yn^*bs@VWl=E2=Y-_Xc*e9On1MadG_532FFMO{+*ef73eN{(?LLiWE4@PZlQz9 z1#(UE_Ql07I4?PrMl^zj*_8C%V)_$g(@V~<#%8R%xZ#_^9cVIzqKzrj-x!a7e~ zXqh+5FZ)0IWF!}2drM;8U(Y{rkblFuA{;2t$dU5$f}RRCCqW(+EkcXR7mC?1%M0VC zgp(J*$$5n4N22$tK%*gFUS-)9mz#aZzpy&L7)H2ty}ZTwwO_K3IZ*5}jW^6h92-sq zPch9yWewFsN2`S4$6~y_pip`WA&7Sh$V&ZnZJ{Dhu0lyHbeZCxcH>im`M8|tqoQ); z?vmmdb#g(-efdAszvQv}vBf93oW!i0B=P;p$({?>z*du;JuC77G=_E(FELV`tg2qr zDp1Q*vJy*KUXD_hduz+67kT6a{-g~29~ZrBIpY2*?~WA+iz|VU0MAJo(QzdFRX&`t z`27*N9j;uwj!V{kjBFii874=Dx(BZgq@V@6Wwv{0WFmBuEJbR=c;M`caT^L^{-~aq zO#k45I%3PcTD?MqogT#r4K6INd6O$vlq!bRDep$u*`6}Q=Vz2gOl`^M6 zh0xSsy#08~DJEMdD{T;cCFoRxM1{%xRzW0!f5HM3>R`+K801OEU|g3YtB}a5(Pk6h z&}k+mu*Uc0EgKZLq%A`zuMu?$M%HnD7|mES1!Wjh=&iKEbpN!7gdjG8IgL9e!VKg- zE;=m>$#%7rVJe<1P6PljL4vHtogDv$V0Y@VjJ+AGa!hXgTC}hV2lDJBb8fN~(0H`w zw-yx3)j2wO_nwltA$RLQtLQz6`q=!`!kE8S4(jLHCcOqDZ-wP&YqGSNY$38QOG?*P zp3xp|9d2)GH1@!Fpf0atH^y>)Zb8AW0jG@-hM->7;(R?35h=$n6~rmh^D`_{i;Jb_ z#a(rIB_$GViQ1r?4_BuuV~SYo?8vhjmRU&lO`ynYnWa264U2JHwn8KMo4^2yO^Ky6 zo`zSy#)Lq87$d=nJvm;L!p8oa&+$j~X%xk>UA8o#fcBUSUH7gOESrciQ+<5$O`r6>)_ zd0u^}DbJwlP(M#zE?f4RfZrJHk?AiD#eX>1nAeiq05sY<{jzriTd>c#kUx*)>Z&ZO z^_rHVdpPjq#S~@kPxDMlWs?)#vtB{*mZ0jCAil4=r4^{M=A7pY$1xpEYfNI7Xr!8OF z_Vk6CU~?pI`9A-$Wld~hN@--zjjE~ATQy{9!F>hsVC9n>d1~P?{wcxogOkL4{FCuZ zL*I@E4&MZBc=HTd0W<4Z^{fG&nu!(Bb{5Z>79wD(Crg-a7$j&&OjfWr_7HdvPh(Lw z*OWCas1<+My2+e625U*QA~I041EZ!`ij6fNYfVov-57J$OQ2)aw~9UywB+dEVakG| z*IT@fIdzlU$}hPoIK^9VkfSUm%LL@(m1GH?FJa(#t;6= zcxbhhrIqOIb}>Qxi_Q>^ex#*T(2$`on(j4{SJ=!Q*@rM$Zvuzh7lVgSn9N#P%eV+y z=MGs-_K%j;2X%fOM>LhWg*v_6tk=cpa{!_laJHs%oCVFW`JiBWXL@M*VEOR3u1d3#{VR|VO ze^ORFCc*ULDw@GH)aiej=`kz&P}2v5;HSaI?}m|6h{W^=FXyH=_n*1ts4dr&l$O}m zg_U|XhgKyQhc~hOcWN%wB{4cMFd#5G!8yzjUy!bg{cNPGXZ&jnavz!eY4Ur_2yf1P zhP&Y|xoO~zB%jH2m6q-i7OTosIu&`X{<|u&vJLy^tAVGJH^#CgNif(|P|(@K7BSzz zX7d#PI0;>&snnF|%Xy|}Q~&OZlidSd-Ce~H_vYmu?-2T*ad0|d=P(u7?Dq)^^&NH6 zP}dqAL<2W`@h1|-%;L*8P<0b*O;#{&e~&}SFj7U#giEOOFkY@hw3b{C$rCzI;fmttk=$sLb3Cy|N-+=$^L@*7=!(1D@KRBW?nD^f1 zpW4O0XS>A4lrR&?XZ zxli_+E^)wIee55R9*_xnmqDYH>+_q7EAq>6Bx!`aD>FLSQ>mt~X=Nh)R$H3TNV@DSdK8D)r*=q($8e*$$f z<46MMO|{q}`u@Tz*$#qYR|O^Mz-=Ib#kOtjNhKt29u%eL9r zJ$8f%gIg=w5`kgmvf^6N9NBRcqmYCLFX?3^c$BUWM+WWU(FU ztcEG$m6Dd?CbcF{v#Uy&8Ia@|YGuPebK;}--!eqNy%Cj{A{-pAYV5kgW;@FQfKUAz zW+uM}>X;U3J4g!4$$z+uy9W=9ygAt;JpO~{$j;B%<$4s8Q{*^ART<}r%Xtz3X-Imh{#=tCGrFzsTpqm z0m^)5OHHUM0o~Q?0WHkyU)Jc+U6sN4Z=k4^z(6U=XvcD90&+7mnhi=2J5e;^g!*UO z=bA6G@YIgsGN_}q0a|r7nB@ixpBua}cxfnUk`m|-&tOpEG(~PrulVO97XKbI4Pr;N z+pvDmi;fiu>P%hKtkGCGI?!~c)o0N6Bj+~*`9HvtQeO?oS>G zE3C~eEH%7Vy0KRb8`*|JK<&B>e&>~!Ft1kLT>HnZz27H+}eRhUK7YQJ2&}`x)mAJc_n4OhSdDf z>b7oOdDD=qQ5fzTAMSl7x*{_-x|UT4+D96Ps)kBi3-oeDaI?_j@kgYIp(s+KzXw01 zT4`pRMJfV=L~pqWJh9Nl(1ot7n#z&iWI_-5SUI0?wDR%YX|0RNO)U(a9H^=3AE&{I zUQ$;xVaei&M=}l4&Ti3imwA(9W{K$q)4N>y2Z=U*haKPDc7R`Tq$0X7sW<`=jJGb2 ze%*ho>eB-9D2p~HzayD{A?m1QOnOR&zu4QJhNBh+DHA9a^<0azNjz;RY(O~bfSzn2 zr$q8T^K=AVe%foiV`eW>vo;MtZx>gZKHxF~lLEqA_w!GE^bY^@#;Ta&gyLwj>i3@r zdy~AQZB*qKXjrmF%BjdW}Q+*@~VCwVTa{cL$!4K}l1 zAf$J^_}Ic{reC;mO!VnBn;Gaszv>IO;h3z0aRBOoP(`S(XbaydaPp;IP@0(As2CRC zV47CzoxAq69BsE7wB?zX`4@KbAG_$IVMY!i>qp36ZjmS9z+m_#WW^4$0Xs`JHjM&arEfz#G^ zS<|GPbK?a8C-#0g8z{W|edA;gzF$x<$kZhH(Xl49OsSmg z1WG3NRDMP59`{|r_TfQJZiYBbk|y$>{}>^UR*+5fu9Wf1Gu}%*!G>g}rlGD5PywL? zfpfe(l+8eZVU2joP}Ec+DQzzz%M@e_+agzE{FDVoJvNTjjA{}v4Ma))nHg>}gYYni z$2$82y4HFn{5=sNvP0CeZS70T1!aId(oMuO@PCF=)?j@Dl$$Ojl_T8{4|FpoHxf_D zT!A*C#g&5nvM5~sH;{TDE59rU~#?L%^_XV6Nx6T2*T1bJpxav@|-OTBh;~K zESmj|vU>3k<+>Y^g;xZViVg@&mRpaKwZnuRxi$97*r%`+y)sXZPy9Om#WX-(ruminLD6@R0zV^&$j*w1jiFlbyeLH~ z4ED0BYMDknS*n8;ya-IE3V55ON#W8|@uG-1j?r2S9j!&7*OzMcDbhgItSTvfAU>*J zhq*O`{goN#S&pzj@~mKdC1LT4Siv9Os4E}Hs*sP>Y|hUd78pO%4$!Wb!1VzKz5DUJVut4CYCj_Ri?8%1+aCg~N}bD+Y)u=GES8iMHSxGb=JQ6q*;yq9}E| zA`X?N0DK|Ah-xg&XVMP1kzui}0IdOm3Q<9#Ac{3I&%>bPsjHKZ zh)o@fPjT7NX0mHMQ(C64EG^H=mn-t}tZIZYzL6nro(GQZc74xB7ngTd8+oIp(=gpT zVAtk9~Ltth?<67xfmBB8HT&Fj9byiqsbM3x>QfA%E%LJACp`qCu`rt`{{o6MbnF1m~~8`w?}Y5M2w?2jQV7pIl|U%K^dzJb-K5n}q;XNkiF(tj!Q%8L6S zE&dO|ku6nV5B;4lU4Wp!!%gUp7;@SpqXI?TtoIo8uwq_*jx~)jUCmOAwQivGL;>j* z_jy~)p{anKCKn@In}&qE#rH!`)30d9wG1(jufo@wMlkd5C-P5+d}JBEEzr)^wjxO% ztBd^d*ES%H-Z5Kt|1*KF`YkA!?4a&{*OZD;ee27e2R_@l_mH*sYM1i3{Ik07Uxo&o zZeF``V^=$uFUCcCluV;Z;UCn`1ya^b_W& z-Uf5hQ~>n>{f%)#`xfe;aCyja5kDBjzq{*o%Sx)yjc%SO>(EVA-Y)tyk8mtmTs z^XTzIhc9@}27Jjb20}Q0T#B*WYf{>w7MiO&Gb7Nj8cS7GL6W`#U%;1l~?RX-u(m`^j42hUP zyxKz~ND;3T_7EhtHHY3uNS2f0(SMEpIC3BTjn!DX5n472`T+3qrmG8X+-m+KffpE% z7`PxB4h8Q{z~~}zlvh>j=?wFICdDx&B*OP#zf=+0c+y)~nvk1WkXD7rth|iU1Z2o0 zS=m|#uL;q{6vP)q{=4;j>)b4PW;=PxhwMyd^XqdiD* zvw$g3I1~nuaEaq^x}HwSDWVSjYZS5D?t^Fn$^}Y4dh7Y)jK6rC@xXl+Ocis0egpNg zn_tVxTyy+o(dbA1$e50=d=o5U`5^Q(gX|&xHBRa%jmYw0`91%l|8W)416Az`opO|2 z{FvOP?bAZS51fXa{M}t2S73qD1$T~CwbxG4V}A4r5f0QR zrCu0r!Gzf&LpJJia&_`wQ)WBB$8!YZz&B^fHmHpMm+7|YaSql2cO2h?XlYs3L<69A zFfNB8!yHc^wm#*($wMDk5?35K(b#Ml>b^YH);;hk1?u>ra(Mw!g#kFtgLu{;Ys41+ z7eSx2F}u-naCd{xq~nOofZv>#wx&p@FW=L6s_FRPsh+T!6EKEZG4;U9H`4+-*pI?` zPydTN&$RbRDdVk1R*CF%PS6D|E3OgXc40FeWKN%qf(kS+oQ>4S8UdO_Q@+6%hj{5q8;w`3n|06qz}(XatLarEsE zJ}gJMRN*^1X)xB0u~Ju=mxl=Ac=FaLd1jP6J~ld$j!kmwG*wf*KSTDKxqY9Hk-Me< zF2X)8Aj~JaDx)B=viCymVAG8}U9Lu{3T_cfQ_sqR#jZawwTfDt5#Ym)rcl*4eFg4l z7DiL3YE=zTc|^!K^Ur<^rTQZult$$x=SOz88=H+iELc5ONg|lAJb@Msp$^$;d(s!|Fgs;ZkRjEy7g4IZe(_xa};H^dszlx&RMs_>x- z$zFbiASx>>G9*QwYlq4b$&pYyG-y%U_mwD$G?J1cwXR-dIwgQN3l(t|W}T2ht!6Bu z$9@II0zOP^mnc9m|A!UvKqJ$ksD{A4Bq9*#UtkH~@<9#5aaJI2d;%wWK@{cyU`3i@^8l(HYS&&Rk&(Gq@USe`VhU#ggZmi`1h;_iB)E|dZ6^KhX zlzK5~yq5?6!pEy@cGYy$?8v!*{W)3vh9I(p^}Fk1{}KE+2?hkbbyR7lDY9}#+3)8r6N{ugml9W!0TM1ViU z9~PW3*VO0<;HHEQ;x)#Tf7I>W13b_q?qRYRaY|Ya+V8qum8ud&7uveP*jKL?-PaD@`3hdthdhDJweplQa5mfX~lQ&s{=rP!oYpKHs z1Q#r5QREP@X4S*wl-;jUTV>VuHlKg)MNx^;(Q%qsWR51!{$s1xrJx9{P8SM z+zZcc#^Wc8e{e&l3!t}H!o(~7ML=4j@!7H@dbVM9JhWEaR24I=C4l?h^Mwr{e5}~a(%7NacSPNtgKWv zJ}oglOdN>jjJW`oku9xT#YhH4N?PU7Q(E-?2%!r2oCVwRVy!rwQ zOeY$twRH7_zQYCDGSvi5B^MQ)IR&aJ#EFE%vbeaLu?H3)LBT@iD$f*SmXUQ<?I%4UUd&_)O6BshHh@5Ki>vmO3bW$VqT_uXUp>6_ z-F*-g0MmE<{P5Huwe4_IWC!Yf7xSH>$Eo=iI(Mb0ig8_rD-hAMg>Miz!IkPm%g0tK z@FETT4!m2a!|)>RrnW{3U$bW3;Meef<{#rXtV7g$ye9J7vD>qkFU`$$jCS2%vutH_ z93O~b=S}PIH{B#N zrY7zam+F`yC;rcCc)&gmsFbz>U6YA^ zsR0!8?5G$xI~N~gi1^o^fBu#{U50*5k;c0RDQY3~?haw}7z6fS*oOnYvya1xL;H7Z zKC;@nA`Wn$7$Ug(*DIfW!;(>E-#gBBoA=oDMd)_+vyJ=^XIjUnF0Nyi=gg6#F#XSx z?ej~>&Pyf@*4srW0V_B}tg8Gasz7=;`@<61u|ZhkuU|B24&WTpAgv$3R1mi*wF$yg z0oli5G!XS6PM$6)&n+m>SOIe5?Q65|^{DFjvcQI}>H7Zqv7&NyeqP~`I$^4Z?_rw_ z@w!Y^WQCWL@Y6#zAtOhEJW`XqTmiWWlx2z&5#O93Sr{RnoRs%Z;7^6}40|XsCD}>r z?x`w3hY7q2Y_nONZLo&^SY7dBm89wyJ+YU=Oi7-B9=#qz>1bM0Dw$$qetCXn*)VD( z{XXPjJ0`gJ?F!iMZR6rlk)ln}#dQuE2ODPDMO*$;E}4HiZlBlYiBMtNy`ugyvW6vl zIu?MxEtYpe( z@@va3u%^vaz=lIM-G(TDX7LTCph=}I?8E@4IJa!OMksSnvI>4F^sQ)XmzORj3kz#h z`S}&>@?F!WWn=dyYQ}T1QJk11-yyLe$n^Ds1@{rZOh9E}#O-11EB9WP2y5+S~+oS3a2sH&|h1z_=&Z`uEzrX@J4=qbj5E%1bD7*13qM-t5 zMy>c$eF<6{Rsy}OEPdXxe@%n)xPS)Ark0fti}mw8gcmt=59%%j9OI!Y|7 z0D^0jNwJGQ5@r>AAlS*%MIT;}P#DqBr>(B&X}F+isr-(W#mSP>M6xjC*=MlXjyXB# z9k*h5K$Em^rmmIJ0pp~gMb?}BrDeIb*0;ePSbtq{soSv6eB21z${0XG)0+p338?Wr zerAT{br^k9-kUt)IC5hacW~XV6~T>m{Y^su!SDu;9%@J>1=D2j6bgE{{w`3xUO(!T zC{r^jLL3Ai#e|*BboF*nD`&9^ri+ji2iZFg+jHg;^=NyqN6W81cE+eSxkp67qgd(XIcoN>RbT{TzLZ%=(# zqiSN5{(VdrB&Xs)8vFHtnN{8;(1SmqE3uRDC;m^%PzqHLQ@>a#{^^thtb-9~^2i7; z5|O}(DKe8qJP&)ASqKw8zV`SalSrP&Z3q1V#aP8reJu6%G|E3L-C{l{ZZv`Vj=%hf zqqozF;HL{bI_QfI(lP2Ts1rChpD3hl|B?s4&-FgWyyx;zKtylKBJBx8Vx+)D4?+r= z;NI>z$(SnJ`Qif=iB7PF${Um$|K5rUVEqetnEyEn?lGAtkYKzLPcTI>8`p^Ob$cL; z$`zV$M5Dhw=B5#f+ybK|RtDfpRZn|3yIWwe6#A15t-PJ;Tu zOqHZnnIAc)0}*4quZZx25!Ws=s$tSvtRF?8-r?ttxXhoDbnb6<>at4f88$F)1eJq<`+z-LsKCZkcW8+TOC&jsi{4isb|o z)vPW;H_jo2Pbt1nw`L1%gPz@AnJCf8W#Z&eb{~HE8n2#DaOkNB9-y9*NOkLpL|~#gY-TzC^-oIl|aeHBBT^g1p^@?MNW$WXkCN6c4eZ z;CctP@j{C?WC_5eS(Ge-UdFYjC*zR%cA%Z!fw7P54(>=_72aFJBx6Dc_*OTsa9C9067*91Rsuv$s3fr zDY#LHFGTGLf!jPVKwV51SJz%#&+${T#S>c@8&~tewEXBb51!_nGFnv75;8qt%zrDQNq`~cnR*OwVCy-hE8KIDvV5Q2`o8>Wcu$X9w40fzM z(&K&jG5d1`Sl=8?%Vf#&KmndQY6TQ7DS@gs{~(Livg6Qd2=s0U35HH6HBbB9jeL-Q zi(>$BXMN}6^Y9j&iY(zPCnwAPv(r|+d|8$Uej&7N4!(0smSA}WE|E-}7Q8VM#^L}m zL$z*1w*G(+wPn8pDI!TgHJM;5$R6vDZqpPYSJDsj0?vae73KL>uL@QrF$bdGW8@~o zvs~e7#g!Orz*WZaDcT4Z_IK@;+6sz=^Jw-sH&%e0-n}`HD_LyQ+@Y+=TO?zoCp7a} z9@iy4Hp(gdDLD1;$s1KZD3!k`R6lx8Y!u(PwS+%qP%qABL{!J4FNx0#6PM?aznx`j zFBMBSv$(VblneQzrzS(wcsFlBrO*_Qq%<>7ijMM(A?h#l*wUS;iiY;nTK};6ZHq>* zrg6D(tNb7p;}4QhT{T7i9Qw{5eMeh&Lr1W*3GcN5_s`K00;p%W*vV&D zK@aB(ze?=ALbZ>l>YtM~l2qfSF2G^_$F5zfKS`yep&L&s#@NYo{5uugT=k zBU*NTDIin_cKA=mM&*mlm_`@N>*_jV`}sin*CUdi|8Vy13UT0=Um#L0D;jy1T4>hB zdnvNNiAy3!Bex`>_nE!7+~3yRI>%Y|R9;r*PA*r+EN6qH2_A(MYIe81T|H{s{&cs@ zqE^0M=Th4e(JpsoM!cZo1F>%4?aZqivrAZ&BIzW~1!`i_5tg%Hf(Q6HjT-zU?IM8v0%TiFscAC8L-`Tw zb$kE?HRTi00V49ppV^m;dkYu>e#?G_&DLy8yWfF_zW{ab)v47JAN-%sLxklc)~~{M z-lCuV{(!XV2H{q=kbBx+a z#joMqGxl>QQeMtcB&QzD`7tn7uEUHqJbAywoh>>WdMW2n&3gq&*erp{G0;Vv6m+hL zFwhbP7K#+8<`*Nw6VqwHHIYi9kjr^|A3b&a`wvw`sQ`jFiV)-t+af)ZBTyM%1wc zIuf8F1Zd%;Xdh;>JBC=TO;?W$Kn>^=!>f%tCccVeK5^swWA<{>#c21<)&R~RzGLUn z3~&`pC|e$-O8TMsXP84Jd0#-wV$Q_efEUOexM3;UDVt0kZ}du=Ys-5oSU*a&$SmSE z?csS&t+d|28GSHFEODE(`yAnG(jH!yROA6!fvAxJb8N@qqzNkPa={8QU8u7ruITu{ z;9EPyIYm}-V-vDsBE_m+ez_|4rYhidUTM@w6t>+>)yKb8#hEOX*`OTcZjE00&6oG~ z5nX4K7&wt*QjhEB_*Svcc;|eZ#{vWN98+mqxuoL?{za2t0-yxq_ddWit*ysi<8cM~ z#WoS5GoUs8+AAEu%b5dTYB$Pr99g=2on~gEOS-V?^y{s=3xRyF)d1&UXKvtU=|44Z zAmV?WkY#UapQf(yHaGDhm)9x~`L6i9PLoRO%|osxv@<;mZCvKF{wCw8_Y`rp(CI0` z*odrwnj5)It?;;Azo(;md67So_`MckOOq31_1=Db z>g!|M2h8q;t$gK(T(flV1#fTgtm2FK~i_z^}2+@g!*uBu>XPY*!91y+y;Zn@dx zU7yxSxrY7R`0VtLi3G<+MZlda zexe=&6*@MlliB*+zS)Mn;tp&1yNm@duQ_x%?}$s_8xi6(G+Z(eP*iU%jH#W8le43# zq3u5-dm}3tj_(L8qJIW#Y;26o|4n6L`Y$RkFA;-^2f&nwLCoIHnTSEz#mM>J=|4n9 zA_h@AV|x=zJ98oiNfT2$XG>=fItd~M5mP7QZ+t^L=YMuN5ovS$gQuhm!yshnWcnW@ zd2uP>pK3G`mX3zzmUi~ahIUSL>g+`P{Qu3vzkB}S?0@A!SW@I4tp8%={}=i@<5@)6 z!^zpyR?^PQ{$E%nQ*+C2eRvR2{`}U4DHRceyrYS!<39pXN*MwS?U=qv$}Rwajj8Ro zctrp3N5r5`#KHWJJl}$<6EQI|GQ#|;oqwcuv;;WYI}&lQ{QLfY2=mW|e}vH{(*3^z z{q}4$f~} zfjG02!2{%dQt@31`vjsGknVq<6i&msvTCMJ$=U{`Z>OA}{{e-%T-AZzIU z?-YiKgX??OKlT!{v@vBN;$Z!MtLPuXf2%{r)Xw}HgM;ngMgNc6{ZEq%f5TbYIGZ|t z|JoQjn~Ioz`=aST9`s)>?hYK{1?h~c-dq)PnV!c5{X{AxOe$pYBUle4c+0D{xAQsQ zNcsvrn$D7y`l^YRW3|qt^%-L2Va6H?iAWHtGZl|(xb+$T)z5mf`P1#XO%Ha68;5Yx z(#y|ud~M7%IJvR$QJsgwPP5%}`#G~O|3j$n1%yJN>jW}YG^Kedjp{!5f(h?_dC6=l zT-Denv>1*{wkDIWcWMpc<hf#IZGW&3uwTBiptAy!-`>+sye8^0jM-#Xi!2_e_1@XYNQ4)yA4NtBg#;QJJ}w%k z_52ZEIfva=t&>rjMgF#kLxa8n(li6NQ9jX&-^CR3LZvS<~Ik8PIi)9TCG!0N$D(u&IJ!s^2M!^*=dUejqa zmz`7JQQ;(8roPS98gH$K+5HgvXw#})t1jhHxNT2&U+RAOE#*5Czl3|7Gu|oxU9O_E z_$Brs@0hL1a7kD}DU+x%M~p4bI{Q;CpW#=`huquOyUnAyOYWnlW}G16jllcL`?Y`? z&Fp8*nq9KRqB)xhzRichfI}ut)3|N*il`Cy28KMviU{YeccJq^y&|NzE^epK!zt7H z@Ip*B6T9)n;&icZ)3H9x_I+O2Vl(UhloFal!Y5t9^A>-!>k23F;~{#FInPr zo+W0;S_FePq~Gu9-jbhu_jO};18d*L2*I+#FEz>UcB_8~9g}~&fWCQMz8A0J^=DY+ z?vF!xAq8^-(HtcVe@BZk>rvq3S`c=b2*|gg{!Q!u5?wfS&h+0lP(Q-$jLD1`A|n%! zGnSXk!n`99uhHD8w<9pD2HDp7VxuHZX1WWKDS=r^>7%-w55HcIZPPEj%IyHv`P+IW zE15CV^dJk(BkBeHQ%lN`86DZ-N|>1A;~0+d=*L^&U3^=*aZ|Qrj^1W-gQ46$-e&Wv zfd90YrZoNLBj%@D)!}5B)gY>Vp z57XP&nqYFbWzdqM(*h9T$n8zZ{u(c^=+lD|%RKgLV9)HK#07ZYz)?-d6DHO4v$&E9 z(#VCf;AMVa^Yt#TUiJ2<;_^8Ii5AiaeB>ua!JzbJRcOg^P{(kD>tTQ;+BL*8D0NTKH-$R7<`H* zu=?Fpv(SIWkH3WE`n^f_Jq1$Ih3Mt9uF6sj5&&VYCuW$QRW7 zJwYPKyCL}0w&cN9X{TAVuG55MK%v=Xu*A;jiGwA%(*6OS(%l3x-;>JjZ`~!wjBZsN z1OnzH`>3sF7s$*az|NtUoSrRKIQ7otgwn{QT(#$!0c*_gGqJES&zE zG@Wv*oyQ!K%|+9w1=j7mn}jXH`sm5p@P06UcMcrjEY1tO?QJ7RsI@~T-CvEt8TUQ( zneO{r(ar&kql`#VeXwdy1xt#v2%h5JuShRj?pSyK)4}z=S`wWeaR`(8@XpF5xsCr1 zpMI)t9<}ln%4lsW`a95CRA#^ljGfP1e-eVf><`|eYzWi3#EKi}++PTy2_T=W*C8TFSIGzLVz51q zxT(YKdWyCQot@rWv@4m6Y_(%`tb$Fqq8hgEqzxc}tXEV(j}?1(fK!i(JCa;r0k0Xu zQH3@ECw8nse~8-;;F|VhIp906W5yb5uqCf!3dy@G9VJAUR>zk_G>u;%Y&S1hh+X zhsq^}qwJKBS|rx;(MZrIko!BJsEMk&x>_R7I^9}MlAO)2`pZUjt%G-KHq;dE^4R75 zA&y;@g938B*a2-Azfyh3InFt^{5a&sf1up$h6JX1f_cck%3fWHwb>kA3ZP`Pgcp%kxO+rO!ga*qVzy6*psL`aFi6&nTZ7t z(98Su{;9ID-&!G+Sk+dL(BgbAwJ%v#%FCoN8RDAb71)s-9{#%l%CE^OMqC~(%j7LI5IT%9g@_nqy_#wpLPygDi#G^vtz2EgJ z^wYo5sY8@>XKpfeaQDIA_{~I2ApIE!p`wf*8c#e?@$~kk^2{-k-VmOZ5BpTdRLM`_ zeQ|-8M)`$?)oKoJQmG43d-#Ezv7q&Jyll_+@=C^8X2#VYBhXX5+q`Xa@4P2_e+@$| z#o7{}sooo|EzkdX`!o?l|5?srdBL9ydULgkHLo=IK; z0$+f4XwEVtx*$;i*9H{Z{F<TF=U*(f1x;z*S9r@$KZ9bCpUe2q2VTP!Ti z=axe8JnFf8JR92xn28-AY>a97?QDtQzUdt)CKKgRNk>uA<~iZ)tZVp`gN!6Mn25(a z!E{2%BST!&^a}%eyrA-o@iZs2c;jCH%Y0HM2kDudY=W)cpS9Kn+(J@&F;#r%rr@D- zG@BH0962_;nhv!)RKWQ)PNofo>99jP>AevTVlbgbkr1s}zzcM;x>pf%)~b#kc_@=l zAWl51eBIu;zSK3=Mj=4!E&Sb?U^Uc4JShuRl-kfXIGVGF`|VGi3_b!yTC~*@F5|B zAYn%*Za6*3z`bf!9b{awFxU3W+K7+n!D9{FqAVjKyfDT_v~jfNdcX^ACuTT3+1Q*h zwPUUj4*Oh^fzVKV8|<5_SiSJJH!F=*YOs$4{niE%1dz16h?b#n_y>vI2lT0K+ku!RqX@fw3=S?n*!Qre7)IKL*c)(eJ!>JG{93hJ;5rbrH3TnPz}?6QLfjOY zQ^iw{&$&G3bKNfGndcZ8=QA=C#k0IH6|ExdxUWBSAPu2%G4cAUZLOGbGC7})h;L%p zUf?cBq8h1PBv)shy8SE!#PF=usiY|@b-F(BFm1y0#C3Y*TJ~sPGSHQiR9cQ18+@3~ zq4D6mhzTmtQd<;>9n7p~Iq2Eg$tNJ|cID~YUq*vpQQe;fqV-qy9=~G!0J{c|>20nw zS#>T-5PkIJv27fGah9B?a;H5HK8?ohGeI{&y!pKSwAy=N*EroElSvocJ9r$b-qu!g zRx0Bt<9bw02*d?(S5~%au{aJ9AqJFeu z?BV5vNt_VO;O0r%xwHei4kh{zKcAC(=pkE7K-F7yOo@OrWvKVl(=6B#r*ZzKyA~TG z+zschYb0ftmrdY*<#l#H4dlW8`D@?M{bVIY2Xl#0eZ=E+`RD?_Q;Dda$xxFf9IY19 zGbQPt?gqkR%?Rvmh!MnKgSU-9o<+(5gpzu8l3~{L%Dgp^MDu``&KdAj6gp)LTpKMk zpbC@zLi99)&Kr&)DT*hk*E_Rqd=S6VF;6>m5%95-dZ3)zp6RFk0F>8@zTEuWGuw?s z@HHZ-TlLFw9K8O#_c&}z7ersc7eQP@wJa$NuD`v!arhxXm#zPX?^HK~|%K5xNQ=FQ1B?8xfVFqk?Mx|A~IslWjL2qRxX z)G?2(iur&AGzrd#a`TTA<{!V@Hccq1Z9+8$B7fxNAJL8lYJUS8MM+cd5s(ZNajR&4 znwXpIhV<-Z58*j=q;^2=Zq;UsE+?sryguw2jQ75HV0!(GO3jTQJp*|_=1hOh= z0o!VVTQ%mhF9#O0S%E<8)uyMZ_%tv76BxFjf5Cj3JdnnUL}G9g88iQqNq79>#BX zQx|Ke?$!^(|@1eukI~30*$@tms8c%04 zM0*cZ4bI9c=iiKL|1VNtm&a8PgAZx}^`fy!y&K4JkI91~r0t(T$XnGGwZXLvjOhBi z*F&G?IQ)Ouk!eA`v$7h}I8_@5F&%r4^I8o<0!%dlHf9F-X%bTKqgm2)(j!&72(r&M(0 zT^pXyGoV(lkr7TKwXetZYCT(sw^oCU^X}FYX(-87@PQ-sB zTVJP0X0#Z9(L=)}c_@F$4D$w|Qzxm5@02~6iE@C$0bsc-hs5#RyH!i~53gl5c-))xB4Grb zG83Fxt7dTUFfa*Igrb^XBwx!SMukh5HJuNF{^fE9 zFg?TBmfhR^UanW}xqd9mTQ{hF$EeToN$r?JU2=aS@C|YYh4m~qk+Xe}bg-f`)?p0Q zTRO}U*lf(ld3Icj2$2vn9hz|uC8^8;ss*MAw*UvPPh+D-O_@NniC$v^ zRGHr6wLmoILE7cV6D#|i8w9Y9dWa~2e1DS=W(*D9mds#a#TGxfg4jJFIQ8X|G_^9E z-Ihzkd2`?SF$P)2V4b#Q=c2QSBa==m_y#>+Z(jH6L7rJ= zRqc`_5j62P1!y87$aB$ni?DOkZ}j~ay#T| zvQgjnt1j!aI)n45tOts3P!wA^Uam%lan-k$@u@9oTU8FyQT6*+F3+6#F64Dw+OUDd zAgtoh)Nt2!pKVd)?OVicTJfJCR%)cNW9q_<312~I-F@H4di04Lj@dLGcMpcgDeZi? z0|(R?`L+=V1~*SFf_($vQ&h#z5$bz2JJ3sI$&08(6Y^p^b%!I{@trc*4=C9_eP%9~Xef9FEr|77&or2NNQ_{Okm4u2!vNe_fEyc~BMtbSg6wL;nG}hhK zfUs>bJ^b<4;R5=`U7z(~3K41Mf_~`NfFMUI^yD$hV4ZuC9T_2SmMhd^g}WA1hoVx| zS&;zdiUK|I(tQvboiSK(YymB;R^Y z56;0fpr;dvR`H{I3dJ;~X>i^5JEIAT;4Py^0)5MBe^ZKKPpZPCfpM_I^?q371y2Jn zVR4$m{@Mjei(akvfhNw?^W}ctEeg|RSmworOz{QFdN!C6-xL>*gOT3&d)d_rWb*5@ z-lu8u9!eVNVmoySO9f9G&o?i>wouitso>3hvPdYgjPuKU4{0L~oQ64aj%!rQc!}*y zO<7J9yt#w-4?Utr$gw!aZR0}|ZCns!cyt9?Y5d)|l0{3;KDV9%NunyC2;SKibKNG2 zP-uUqKmyNLkx9<5$|0J04CNX1sV+=x7q~$XV@k2XI-R_1hL-5(z1T`LUCl@Jxz@v) z;h4`0&bzG8vH~xPDGiewOjQ}BxmAf7R?KDWvlOZn9Fn=E<6Tmyw|}E9>-&~)NB=X_ zK1-i6R81$wa`K!K{xh9aG!<*^9j{t}p~Q~a?@3_R4hKYO;c+5$j~W6%*> zjwG|))x%_z1n^w)K}_V(ydyo_5$%z*bxh9`9~2m&Ek#OaOK+JJue@|YeeV{C|wVi1oy&JB&l=TpYG8M{;}+7p}L9;W-+ zyq%~UERRDHR`kmEOm(b+Ku!u8ZHZAoLckKZ$2O|Y&sklezaIlB&n<@63BQ{DjisV1 zUrWi=a$Rd*f5rnAH~w7b+hpggUTGaMRtsmUVN^SJYgNsk{n`YMIteg9%_-iz9#PvV!<2n9ViS#y2h{P9cgzV(#77v5*Ng!MaudS zz{vqDF&#xW&Um>=Qbuf+qp6-RdPBLnX$X0{==DSPS(oGCUFghSF!h%-$D6$iAF%=a z2ssO=^1HE)&X2Ccu*J->kI2_SJ=w{{;vqeuHjNzg$xynMC|X(?s3Kb0a?PcsadyJq zw_b_XjzX4dsdQz97=@45VLv|Zck-P_PR73^JHE~x`;7B=YeG1ItbIz=$Elt?@|*l! z`FuB*8Tmtz7(VA&sCxKv9QFawVZHJ=HAs%k-IJLKFQ_dO{!Ow+g(2CemBv&!TmBolerR1SpXt8cRpiQRd4(7A&aqW2dJ8gb80cSt99 zg!!3l+X=t|K@?R1T0(#gR%B>DZfM}g5e0q9&&834P8+0%W~yjPo8H0E+d76xifCFIur=NyZR9BODM#n%7C_6E^RT#D~J@jQC3y%byhlN+J2OqqTzGd9Sk)3 z(jNBuyKawS@lmk1I)4L|E1ucc%=vY>fa3r(VQ}**bR0sdd`%_c z(+TF|Ezy9SE#UT`fzeV(ALEF#@Qy;q)UeI&Pwy+wuOVq3`re2<)2Mi$r!|v>kQ;-hP@Vw#x2SY$yIoV$|U4> zJn=ZYPHbi5AAo45ldhK5+hpDt1=R0~UAfFTe6v63`DZ35*aMgy{Vb$ZD*V-x%AV$v zmM9koRZo5pW*#RlL45>KC7?I+}3bk1wX@N66d+DfZ#_*p~VqEQF*G+WO z=Mxn%zZ?l>pPsn%8K%+($Z_`|^v$0-YjPhm$Y@DmV$^2d=(H)Q^_C(~%kf{R&zFx? zF|{oZSfNlyM5UD`rC1_0CKnA7;X7C`r^#e=ND+@`f0mdJ!WXK7n8LGBuKwx-Bv6FwEJE8SL8$s-V*BH8rCHjETf zQpEMt=LbLVM}|Fefa6Oocs9oB?`{#OVOT>EAZn)Z>gy#ASE)D9&~Q@Lml_?QkZbIc z#2GO|twglG&?NoPab_$~uh|X#0C;a0$(`kBOwV-6Djwbz`&ZE%ZWeW0(jV$h0b>Cd z8`TwQYBLK(oK%3OC!h(WkFE|cf|*4vbOl#b1+yz~WU|_l>DY=t3G%NLTBu^76$P^{ z5r_2JV$BDuykLTHMxpn(Bm-oI50a&iud@!;dl)2GJowJqT=s?-f`$JgQrO#b3*lSy zf>YuztEXlVF2z5AGB*#1Sf!9E3z-fjrI0%syEuft43VU+SGfU~qnKD|pt&7~fj++x z_^YPI>dSu2KX0{cUqAZ2pWbFsG+%pqJ9^iQ&q|#cw4Yzw25AN6Kd*j(k>MA)cTJQ5+Vu1@(A zn~_l8ez(}7laZm3_hGq-|$PQyp9jE;3V7Kla1pWDJeYM3JnPb%>U1?_mx>7bi z%imF_FxX5Dpy_Z^rU`2XsI!qSn4_jzRTW9}Z25?kvJ+D?182@$t=z^Tx$JH16BH8+ zcFMHCkU{YfVqx?N|5wsmpye#;43CBHu zp$%#W>Tf#uv1AL>#=u{6{4+5G=bnXL7{7zzFPtcQ41`P>u&OH?RlH&d{F(qYO*I-! zBzHCcT<|1vfASiXb0LLA)+{e#ViU@;Dlny;5Eqv*(vkrP2fHu9bPBcynj?Y|1#oz$ zt5^(?bbkm*;Gxv8uLg+33B5x8Jo-9ft5h$43q1oLk%$t~7k69JJiT?>Gdt#`t(HZn z#z)Xv>y^FJf(DH{`dS`_k!?EvX6_1p9U6M*?uE6@nH4K5=0|lnXSpsG!srbR*OuQx z+kzL&A}Mdq7d&{Ys4lUB73K4+?>$Q`!G!d}@krT!`(^Nz0Tj+RY1r`Bk%=> zN8Y)RHCjgU(~eJ7a8!NS9d#EoI){<4L*C5PqH3tSq>_-b6JlF_ z#?)l!|4hRT2)zI>ATM5Z%}=8=i#rE5png?+#9<$Vw1;-d8E}+Gp)*q~c#Mnn@lFTv zzNnkI^#xteX%5mXuA?hM-}H$L@yX#qwLkmNjyLGk7g*36608b)@`qn&etJjHoKH9w z4DZ@Qy&YEf#>K^LbACzpj!!nzZxKLty}jIA^)8pSzc)L$Ic<;nxka;aX;nHg#HJf{Q&#cjQp1S#V+4l12tPF_E*zj^^V{ zh=~UBrVoD-$S0ZSI}&n4i{(cUY*q`G=~9WOBoQ4e$Hzb$AC7TW5eG;{8ZZpJI%m=5 z6i`+glu;g>U=r124!baxqo3XdIoQ15uSjsiW$4+w5`Z30CmIZ3kYdAcg0e`ofN!Iv z(jH2(0l>;7DvhWeBUKw;F(7|NN0H5q1iOo&Pf;O^9nlCux2efnm;6yZY;O4t%7M=0 zD2#lIa{Pxde!v9Dpbfa1y3lmc0>wnKL?Z%aqZfgMF)E%Sp!79*B)gLp1d~*w?oLEU zRW&wesn-=B%Uu-a1j`anTv5+7LCv~m$V#htpF3KMg67Y-)vCOrCxdc|Qz5hvTB{*C z28zH$vr(Em!J7*X^d1wW%_cck-1^tU-@lz*`krK?vR4R!SVYrC7T7SPG%F(rXF5)TLFc&lQmjy~`{}9|ltX%68nBqh zXa=j1$VH|__d@OmD!&det$?rrx6YVFyQ4B9Qi=V3^2%nm9?1)vla2?vf+3E{) zb^qbiz%PFyWH`4h9xs(2x;xjYSqgVsm_g}0R1m#ImWboliD+Ebn0cnrLBWrid5%Xt z`elHeZMK6!_B#fIV`B$&f+ORn*lN-)?VMadn0}xAZA%bJ&We8WGpz2lIyteZ2)tq9u-?cOptmSjN$SIn?Po!fFnicH$}8`oe@& z3wey3Mxe!ct74Z3jWf;nCw-`0RITk2l#I!^rF>))vu$Il_7vg)Dk2y z#mj~N(ljX~8!R%EDb*RZK*yww9?TCf8IVyz!cDf*v|3|)285urrm|DUWv1y#!I@g` z&~qXvVNBO`{boJ(M!$t|la&~Alqbf^30x)b0FhKX#8^gITpCb7eh$@@p07E=%0u#~ zO?}x(!O<8OWlcsjkV8n`>nutUp3E>GZxK$jc z`hZ$f5LQ>3mT9Absfy6Y(?9%r*vB?lhc!Fdc<7q6latf-!30|j!5~k1{hDoTL{DQP?WH`Sa3)wA>^7{#ha|(c=EL zw~?5uNVZDFm=Cg|VUiAalTx9-)>dmiy$Bx15d_^XwI&tgQduu6H{=4!C!1Xe2H!mcgi0ldURmiY&oBTz?bl8%(qMZ05D0vj!G_uYM9l> zasAc~AmdzrWO+Xi><34@w6EJqo=30C^0DVJ#8?L?A9c==?ZDR~64H$b`>1_R<*!9GA!`L~hAkZq z+F+6_QV}!sLnpj)(LMo?3ih-?Riv8i-R0qgYAvH5(IxQsz`*OH(?I@LlUw3P9%XtR_);2MRSenkvETm=O{d@4eJ$3Y5zWOXA1A!0b_%n@7L z8H2LGps|fv#U|O0Y6=_I3ZhXO(FPC?MZxc3p-@Tb6Qw}%dq?;>iFErwc1o&Tzkp-$ zY;a<*#SUH?>x=ky-0V%@)|{bVKejv*#)6c8pR3yI-p$r>Z022et=ch0uJ*U! zbH)|hkuVT5tE-&@>HX?Cdu%9lTmi&lYBr3d?%>wVl@nFCAD?Tf-Q6Z(I3OlGOe2y4 zR*f|?rP>-2;7KJ&=!Al$Q?ex9lxuqW-q4MzgeFLdVv@opfD_O}f6~A`tbwR=BBNt* z+jIz=uzlY>B8iC}29?*|U`gAEvJrytzY$VwsAE_tK8hJ?ifQ&MKJ7TFKoi!dHQPN8 zC{HDOXJabvYVYh1$4W@K?H5QzP|8WRXcYo}n5cS`-0a5xOxK^hq@R7xGIhHa_%$;c zA%LcQ?cKEB+%^s$ha@vKNhMUZ)X92D)x z;jiOE4|_~Yhu)hm8V(A@AOh=*Z>xib%?}61lpgQ<#ql=~B1wkWu8H};>Lhh4+{;S; zDQ(PauW!dqZvS1=r6c3{c59^L@fOYRvOCiMIm?@hb}hRD{w5B+hx$RBq?9Z}wpd&n zslZjquSPaHIx&4>MMuNY$nI)s4M%~r7*2Biu5#>W^I2bBVavf6S0NBa{xXQmxZn8Q z{Cck(&cDCPMAtP0MFZ16##GjdaZ&*#J+b26(;mj5Gx%6>*a_wK>*D;4jNQ;OaRLc-UQ1=9aagey6SR?p#LyT)XSD% z>^ZLx26=vB$p^M0c?7I31V>`3Hv%}`y4UFs>-Af~{l9V`Ydx6|K8+Pw@dH|sx-{`Mq2{lpO zr5vY(I_X!}7g^$R^zFvz|8od`X(m-1I@kY`OsaDXMiX9I5P zso{auo_aw$`FM=(b}~Zk4QY-XwertY^I^(a%j4fUV=^Fy7Hyy=Kx&k zw(a|Na_sUfnq}J9Zb}7z;{eEl)bA4zV}4lE+eg%&2upXuE>S%0{py5nYaN@ zIYP-Jo9?*>3B7C|gv=K-|9w71*@Nej)Ul}B_JRkmHb&&UV3V}0N+VfwO48f>#D6$v z_D2bo<2~_iqH?Y)G$fC&V?Le@s(IgIiorDd8EL#7s9|fd7x!RV^!Z?Id}LAzRmt$Q zg=p$cxQ-&C260r#FFmU&UPJQCT*f(u7v!Xnd<97^`+i%& zW`V2HQoAe0e8F6{6tMa46$LcJ#yyA=HVD_mRT&`|yqk8P!qY-VEuW-l#=0lC@tnM7 zF|W=R617)Yg;M}wu;QluSrrBAQGQs+qwZso;=retevjo4iw}_KB1eB*2j7N`v(B$T zARn-?E@7$rN?#kQLO=%1yAeW0Q^~QXk)(`t#%wnjn2dS+u?u^8-*9B)=1-p#Wk?1q6x?i$+S zkpn$rk1^i2>RgeEIc`JOE4b&!?IQMmP^hWBc|j};12ILgu1~WMhOPx!u7G`5oCoEw}<&} z6aM)@1If*A^UHL)Kd)@|LjZS7y2(4NS9xA$r%6@jbF4-2GF2;hgRHqHW#%VFQJ*2r zg@IG2x{OBAudOH<_Mxw(ZMN2b(e1oSJ3haiSz^vHw^)oOHN3ryFXVc!9WE59w7$Kp zqvdjkd(s(=2ECn+zkznJ#e}^>b^k&(zO!TRJQ*DWD=p)n&>h`>hVK4OHrhHE{ze}E zA`1EVRrypzDFmz>Y)$^)%>WqNUz{Ek3-fPG?JpWqN>^Xr%0k!jFN?5=t)0EVH(gsi z1^_T8sr!!v9X&GuD0~Oz0GK2mGk_(02QvTq04(p;KaPL5{$=?OcTn%|{pZhr|KvUY z@27vu^GBQB(|_6iy8Ex~`|s?(+W~kArTKTp^L^m{Wd42+`a2=`*U$pcK0x|60{L%~ z@CW+%KAP`&-q}Yybt+~iCII_L%Lc#?nHc~lHd+96`k&cH7A64R_)qqco)It+|70KW znAn*9WFG;u`zP%P#rPNP$P9oT|D+r7Sm_ylH{%^{q{m}n0rX4$?;U+N{Mpez;YQX! zG6As4ch->(@Dd%MrvC|P^iYG2R-8-bK3HF={Q2|e+0QeV$%&P7H2b?Y45O_4AYi^= zAx0m|PXvPK#u_n&zQY*VlVk$28dO^Nt>1rqs`_M>gFNm>r9CybTt#6BlHzM_C;|I5 zUczun2#fkD-OlQcv37~k({AG7y!qhu;H~qG=dIat>ADN>f6z*Z==TcM_Uz1yOk*1S z>!sF(^7X9S{*7(d+cWS+uvzZ*@X|`JUasTW4xQfnvAfl~=-akyVEi$OkMk8;x$C4z zO^&dJ7ynmvS13>y59LrybYO*KxOEHkviMGhZz@sj@qxWS5)a9Ho~RImt+A*G zM9_6(QNHA^=OA$P$BO}?C4q+6%$S$^CxXYCnizyj+GG+g83TS=#&&1_!^H2NLOc21)}4pKF#=i=+!gm#?F^zqeVvlT4zL7 zg-2WEI#z|erLwBZp*Ri}IGmo(_xz$PhLfT0sFG^-W6yGwu-fGUA`m`-uD5*|rpZ=r zpZ(*{=`he5@Z>a=v0t+6VrlI(s}t?YKIP6 zLk?S#Kn&x5cK!ZzVX5GHnV$bJsYoqDI&CryzbOt68Z^lbnkK9sE4?8*S+Q`o#Z3-u z{w${hqzYWt+a%Gn4t(OTqGX9A58v7D=Luv0Y-aU{57QMnoC&A@_Be6zwyNB_N3+4m z7oJr5ker?HSgV*1+c&Z@M7TU5jX@!$f&H}=6U-}PeyX?Uv`6kTx=siq26@fv$SbGQ z=zfUR=%_?p2Zt87|C`S>#Z#W=8e-WruVp|h10I}CozKoWaG4cmzr`BL^<=EGM4!iu z3w>mTw@cnEYXyioVSWu;kYT+k=)+%Bse?T;{`^z4s18sI*h0&>>L>^S8{- zQ;+81rLS!_-oLgFZT48)I^WhzD8GzP_HHv7w0zigTy(%7LjY^?2pdfkGTNYPvc(`@ z#x#!T?nwAvxtyY90oaPzn%7@_S*G~~mVFLhdE{nHR_PpOue3QpxQ@cBK#f!{gsZN6 zSR&w{XlfGg^H`v=T%B4%SRu^Eu;#`NzCx=>o~Q6`(`a)CJ1SV=Uh+ML)HPo9n;T*M z_DGDih_e+gN1J|Oz8dz#S#=@(cIO}GH_CqFrXxu(Hx4S5DPzL=1ARn~8yf^)C4D6t z`%F;$$7Z#yDhIv7dqLPdc6bmVVf!RgOx7Swe0(H%p|^WNo2rdeM-MBl0YCrbK`4jm z)c^Yl1z=`s5EQD0&hta<{3vnC8U2wM{ zqf$_m>q>`<4tZop6o+z6Z1M4k?vpyp3`^8Nd6(@bjt<2IYZdN2+Xd7GO&6QD$QJ20 zcSs0|tu4Im(&~2DGUUE2oyO{hH4=!I)JCg~Hp8b|szPcFR)-^auV@@OYV8;C2O872 zqc8Oy?V;;Z946rH>TG3c1D%;QP5kR9l|RE5ZO|eSIW7?pQ(Sd)xhTtm>Q5)xdGjb# z)v;Spy=&_N%Bus)ZICDA$~K}%LC*Pjq?u^RE9IvQMP{os@W6A^v>jXK^rjy4yMd&V zG>t@N?Dot#JzoNxIn}wp?1AC$T0o|L57isGyIEOxM(=D=;p}C#5%8g!z_$d8%^wnC zE9&(n1&^3<_QE(FddR_ap#~8a>-7Pa_CO|II%l$sH9V%$uGTf)nfn1P)}-M{ZMdQ(#kJlcEcxLelShO{~H%@l8#Y(fO zQA>wAw-wHSAVh6M3FH+T<)S~7?~D|};w$$_;%c!M`!V)Zd(I=I4BVR@k>uVEGM{qG zP_DXV=F~OY1&3|Iv1Gve@)UwIg(bSOGeHmxjd`kRU0HNu!jVwFF^aI zO!Xk4C-fE3IWx<|Y8VZeUzIo}B+cbvw2tfgvN3^>BC(pmaHT^QFaWz1$BDXs4Wa0R zITifT1A7{1nsGN!g;|=LgpqJ5w;BeF4Wy2&Pzt@kY3HBItHvF8qcBU?e`v%sskdMJ zYNKO3{mG@4*17_pWk-}(de265?uz-$2Hj3%4h7C{JIhf$j0rCUa+xIae0H-Cmn7a~xGjqJ3I3wVeHt@Cz=yVy z?KW%QIfF5-hZV+mwm*3gl_(wjYNK>_a2VSOs{(a#3rKAc%8m(fOPsl|RIM00m0EO& z3XhU=9~QiQ#Oo8Y(SvH zK?)JBg?YCe{K~xsTK%jKGvx*Q0$~63`|nD7d8~(x#yf&6ozZcPVzc+Wk<=2}GLay{5D@fHd+;`U+>=;Sto+b;E5}nKK2SSPG zk^N5cKRDi`vBNC$qwbNms%CC(_}Idy&u+4}RBlhWZ9Yi^;XZ8*qHK#bQ3+BPomlQV z;!Q^H$ypWjSfbS?mJZ}#tm#hK7qNf2Dj0Ytc0Y=>O4z}eLWW&qBLXd+C zCi{oE-2DM8f(CeBx&Y`04Yf4<*gbtXjY@b_J9hdfS|D@K3?Mk5L7+S@bnU=PLv&pK zJN!)lIf&gkf-MqKP7uOhFkt=}>=86?*T6^DkK4na%{*D|0f8oAJs-I(MqpGcD^VQP zquws68Hf4FKQd|)hdm}+XTgcr zFz8}fKrf#lC+$5cftttcRd-=w1u_YXQB3Glho>v7b%u{34`8f(3+OS>Sy_T`8-svH zayDhIb0qmziD*yHM~Xz7Sj82QfC$)a3}@S>3I} zTLY5XkyG{SS(e*3`kC+JqaCy?X(w=NtqpJ1-yG@NHVsq=Vt%d=o{{iUuEYOsfxzRa>0IH(;B(PPw=9tg2Fh}yQ=~1tr7;h#@ zuLd@8#)Q8pd#n3J`$n(_Z;c2ioXTle7!}VJ1Ml1EPIwaJC>4af$tK539AYxv^>dE& z%FX+6QgSSrT$oy?GRjCV%O%|eRqJ!9ao`6na8qMrRO3@xbEF)fu6gfh9Fumct$S`S zcu%W^{=~Lm0k!tb?%LsZ@r`3p)Js{cO znkIW&uXPIshjCO5X>Ja3wqRz?bVqj9i>4QBpIAX+>FZP)gXWNxbaHb!W2EJ+2k$jHRaTlGXHKN~+p;|nc zuw>3rUn82+2U$U*F!9C~G2;m;0yk@@H1xsJ2>>~5s=3laJTw%`+3)J@=t{0r5V<*7 zNb3wyAP<+tEw=QdYgt)gW>_fJ_|@O)npP*{lAG=3`qVR{*$mh+W7T9~w89)M;kmk@ zP2_>_MN5-0S@St{yusXdzb`|S7AyRTfUm{h@zI6=p6}xI$(9T?^nnn6&uIi2aTPvr z?=W$5Qrbtf5i&8FoZ+6md{B4*PHLwq^nSHJ6!Y~bhM9im@uKU}>)FU=DN|_&5%SMv zS3j~8RpN%L{Q9eJ66bBCr&Q%MmC1$f?3L%_W)h{=E2D0SRKK1s?izr4klbkB$S1sG zynY_|BdmOp5Bw0)H!Q7b{j6bb4n6$omsHi;iou5mIJGg{g5^OrM@6Azr4uTYL>kF4 z^|!8q?)ucK@sUdZ0SxaKo(>ToV;{mU$jyZ=E1xKD3em1ln_xY>;FI9)(5j&3z&P#M zFSHqoE67{cc0Anq6FkQY;WEXM-$*39ee#vzI55CiP*VMbaijkR(;ZN5o+uvMRfBEYE zC(o&{l&G92g`%yYo`e23LwhL&Dt;?-gWt}wzx;eqEbpbu|2?4G|H6ez_cs?R11;UZ zxKJ4Yp1SwrPgm&gdnQ(9ynj9Z%<<3bKT2X|VEDr!$V^9%$ISTdL1tooKmYk*VtP+8 z1L|O*1>|IXuZ#JQ7<%t-yys$NW&D>`y<3^y%VnjnFG07T>iMB)R~ z$ACxA_C61^fGZOoD-#>Q26)EI4E4KqMnG^kMg}@O1~x!X*Z^+>((fVLn3w=>Gce-) z7f0)Vp1i*%;P-_5!_oTR&e^|tTmjMM=-#8v(F0md_Z}pUjg|Jl4K~Nd00<)Ym&27F z;JN+F;mShK3iXd*bAOL62N`p~nE2w)Cr^V??b-40mR1INg6bVXNk*LL>8RIGQUl~X-M_epYL;^&+d z;i{=Kt+!&KtP5^$EhcwtZs%8D-MGBW(rYawL>TB|*8B;(ODjxF2_GlW2iLqb9oCp- zY)7T=!IZ7OnKhHJ?K>e6tWMTEkYdK_?;+!7WY6yx+ziuw?tMv9P7{sPWj-sc)2c0} zU}LV)bib-s$&aMqux@)yDX5se8h&EEeQx@hV&q7G&j4kuFzI+YZ}Aw7O#A3qXJI0p z4lkE?KDSF3`+a!_`4(!(mLSHI06|<)UQ~J89tErS#@AB=FK>17w(z6b5?d_!aUys1 zQ;o6e0Xz-YZQ*53AGx0#d%!`;_0VHhJkf7=#^b41KZ;0*fen2Y%Oh+G+MC@T9#;<9e3tGI8eC zq=UKY09tpt+J2RtewmJYid?bwVIF&lgOlTIwOhS%qT_Y^xOvl|DxP}p`!9(P9o41f z6zlzJ6g3o36lZ0uvX=${qR*Zh@oOaDpz{Hh`F;zZMwkL@!||iAMowp@ISg9MK3dcWF%Ju?eHThjESB6o@-*R;$4*Qv55WO$%H_7KB$-p8r zgnvctC7!z+MVY#PNt{&~4672LW{Z_ZE&RwpQ=AEx9YKLAa8q-L`1#<+0?FM^(-mJr zAKq`dq|z0^1AY-VC?ZC&4TWJtlCe|!mMf3#8x6aM8)Azs7VH)3=!1^o20C^Z0eyT! z_p}YQWhQ2qi(>pq%+_&LZ>0QWT107hEwK!S_is0#IFvg0eg^1ov7K^hJ2oT_L3YJ< zqDN_dyPm&@*o38{<8~Q-wT3;KzY_l)iIJ3|W&i`YR|igZ-EwN}q9 zI6z)MWH}Y_FrGsY*CcZ>kM9Mf%nv3~MFoDLx3fLEVUw@eprHOfNc^Ie5n+>;d~%6J z?p%G>;F(ZnmS*|&Tyvw28RH=3B1emQc4Y;-Q7TI2M!B!h@~tld#yk?unS%fFrxsBe zyX_V*0@^Rd%ECJ=zU_8G z%X!DnyeAY#ex4LM3AG8V8AaYSDWI?nl|6~NRk@1iPC|JMJKcNn!_?ck==4Rt41nl6ms|H9S%U$j3V7I zyB)U|33MCEkzfs&X?Dy}w9Wlc5pggnG_O&)RjxcVtX-epMZUbI4na~Z#7mJc_HeXq z_>`_{WtG36u(YzcGRxE9mcEed>K3J_a@PI7(Jf(x3Mrw5r~dR?e9+}?)dRhn5{ zw_wVh%$`f_n;p2}$b;{3LhiUrBds=X5NgA3EH%LygjyM-LeJ;4;~D`w^}FPkgwtcV zz4H+VT;dJ3siG{60hHTNLzJ&-X*9&X=mbWdNb3y#WRZCDdQFjdI7oCkTd_k&v*Fz3 zR%F|)A-L3tl}LymM*3*!9=uK>1iLv>Y}Re3=qPTL^aveS88kwwalNEHo?fKbZ4g<8 z@!~{Dh6xg*hab1h^QeP%KhZ=MM03W_gWjt@92x zVLl{3OTV(c>g)Pg^WH1B$+v0Nh}o#coX1Rj#$kMx*#X`e95FET`_&@G!fzXf!M;Kj>N4;s!k4cvu~9}v<9fy~@z z&0=PhLN)k3QUAx%SWKU#IHPYP9^@>-z)etnbDan}KerMXv^WYv2}FFTZVU#qEl5%? zk@#0gG=$PV_G+PBNPC2A0fSYICTl@#li+?{>@S!-gVCyG_#dTB6*R})S3fN?&|2SO zT0;x8FDHiBg|u#XUE`PVLrfz|Xg=@4c3Z1Pj+E1jhzo9tizOXKC3bz_hTvtn?v{&& zNTN>E&2Ua``r7t&w;8CIz#;ELJ(n1Ab#V9>B~l78RRKz&ub|W4mFWiyY>9vN6-)<# zfBe|}62W{ob@wx>7-W3?dmqO#+Oi#_KMgNjRHyI^zPCm-E{YWi$(fNKKWgNryiK;x zl3i%nbUNHrYO0sBzQc|1yyd);iuRa+BA4fP?Wm-wVD@?jbT@zSvn<3M=^}AZcKC|G z(@ALfE_?)uKBH=ZHla2dcR^$}^>m2~NMIkN_!c1Gh6ZUcH8(L_Ht|TpdjTOC8a`C( zP1Rnt`5_}|(NMlbet|NsOUH0A5VWW_^WiV#w^?LuI3OkMO%R`>&u|Sx%C10TRTaO#9khT{V-Pzgu7-jlX=8fHnT3dgF}03I0DQQ$vF?nxpcX2 z0n~?l<}fCu*fmW9yR^yZJc`1CbVoVtvj~`2u0mYc0u8i*qbZ*S){zst(qfm^HHKM8 z$weV;(>lFbZm~Ad+-z@y)^G_y*9#sJX9ho7J&jemvYdT>j_R!OwSA{A$bExwE9 z>V8z$xNH6fOqH9cFkXsUy7y&p>4X<%Uu?ex=}h5_;q1zCoocq;z`^Kr?C8gKt!=9X z-U=^B660`F!T6*_R-aClEpHRk5_79V1jEY7>;kQ=t8Bkf?Sv2mPI9VT@UEXac4)-r z*N=0JK_7!Y#>EDz;zes|DAIFm98>8&g{cIZj=0&Kx0$FDoPaDNIAbJ>kR1xWvFvx8) z05e^pRtu;R+m(uTsT!6s*!F!(Kmk$%$`MU-sc*wrLr5YjKo)%-5=~|wDw9bZ_!(0Z zgXkcdnZ$%-uB9#wiM$e;4#c)vJSE6*ux4A?Aau+(d^T&PhZ0<@UF+U8>C9J;@QM2LQ*5un%N_`%z)5 z-5JVRW&-Il*ls}YqB4>Qd@WLhl8`G_I*I}N85^xv43s!sF59n3wClZuX0;d+h7V8 zu;I$sKy}h4a_lH@J&Sj}{>F>o;FrLpn}AT3xM{?ROSn~UQ{y(8{P5z|W*C57xW#f~ zVWbgLfZ35Z-Nh65JybPw1nHY4xfQw;1NJBNju?kY646U)wxC`}g1?lDCr;;{D1h}V zf>TX~*qBw~G)EHF26aOug%qYW;}Mo+op_@AW0OEP_UWqX(Xp}`u-GbM8!#hu|S;NC{fV*mAt#TK!klA}Cknm(*ptVB+1 z^)^}7iuizj0WT#9>B0?@4lLE!Hz-i1#U3III(e)%7 z47rDBya3w!1ZffO7T_h}c5SbhZVeMgKWseFXAHeNy=f3~ce%znXooVa(`OCQLJ|Xw z^tm34xM+i$E*KT?yYsE*=FgX&YAYH;x?Rt3{LY1iYEo9h@a%dCpcD~rBzD$me5Ii} z-?*uS7FJxe%@S50hESaS_`cW6s0_NKe$y`L$%%q4eZh@g(2Lw7r|g>0N%GsR79TX& zk8p`)>K8o$E&LWC{YeTS4tNE6!HO}!gkrEMObR;VP3V&&4=sJGQ|Z^qa^Oxn=BXDa zix#U5Q+frM-&}g=!#$xMvkEr1)xkD4uH`^hhI%wRMFM-n5tT2OPZp_eo3 zRl02NS!iHQdAOb_U$FLj5X&W2qP!w5@XRL%>LP9mnd?^(_*2)4 z8&**o0GYx+P#4AGrDpTzP#@(2e-({~qvs`3@=~ncqg)h*nD3ju>Q&w%Y*iOf#dQTiJy7-(Sr?`swDBRSu?3VN zK@jQZV5W4cZA>J9{KJVq5--MtI>2BB)1! zXF=*0{y;3!7kb=Z^jmKO0*K%DWm7aUG;czFGn4Rur+Tv}K#95m3XlXIyysKD< zntqE*^`7rV`GdASw(A6#)jc#~STxE|;`w5S+ftfIqSn})&BI+AL<**dhk{=GhWc;! zVH4%v4ZpCqCcJ!;ONrbF`Q z_qQk4h^`T65E!4JF(}CcR|tmG0;1Uk@`GUGza%*oI-QgH%B+eHTVUy9q61#)=Voo* zPCFDH&orALbf|rjRIKgm^0Tzta(K&Dc6uw|l&y*jp8c0jD`XQjlK+Q`t95j4>p3Z(LxW&fZ&|1P-C(^cVS~By$2obXGT}MpdWq91 zP2Nsdey;osxpKjJH-e9TF5>o6Dg=1Z*2j;ip`0|G@s zj(<754P;~Cb~uYuT1uQqsU1PP{#N+Ny{`E6rq9QCqlR1iKIj9kX{9GSa#CD1%kSFu?&v(?I9Os24Q9~|OSFQ6TY8r=}7WLfh4jTG@= z_MP4OPC`W$=_o$w=`@(H4KB$;in*0xQ=L-mAN?Y#q|)g{_GhINNWFle{PO+#I(N%! z3r%+}AU<#hn-7Ios|ttBY1DTps|1sRhvi|0<+tfbJCqO>G_F@p#hQD6`UB~l(e&NB zZZ4_;Y5hi<%IXQ{S+a&aYY3SV1hRwtfN5odzGfP!O$2ti&e9)3VmPiAf%}Nt`c<$* z)sfrsAmZsYXj|c`D;6te$FAZ}KjE}b@t|h-+K9dN6e8${HNH#^dG)5)- zlQL%qQvTVFen{mfLqpdgy*$D*s;ObBWU0_F4Kg*w5e*M)I}i<%+ja?_>4EOM?%SK( z+#g40Z%mXKHFBEq>M&|4AECoGLu%w=C}40rB;8D1+0{X7cgRK(Tj;R`Xtq6Zp=%Qeq7#&}l-#jp_!d;v#2@nwzDx!TcX9vB%A-|5nFsgXErVU` zHiTa+JxRZ{e9~(D1};K1D^7CAe9Cl_9GMcC8Yq=R4(efCxEPV87XaHg#AuRXpWJ6d zZJ-=BOMV&KeR{!BG_DD*z#@p-)k>huttVSXjxuQ%)UDe32&dquD)5AAbTf)9y>q*# z>L!lX!QiDBm}WnUj-E8<;Jp+=NCTccl1R$>bD@!2dE;8a;yUeCCFyDXfY#*1%ryDB zAK^J_&1tr?3X%@wOE2eXSX?5rdkCs;Jv+7bJq%SIzUmnx86}1+DuOuG3p$*%+IUs8 zj$8mnv2bxR?;}!LbjUD53>6|pL|$^ZGGb{Uc`NlyZ9Af*9k{gvD2;ybbpM$(3P(Fc zF)gvN(nmB89(h>@6!(j>L%+`Ww`b=UOJMrOh19Xi2<*)iEw{Z)?s(^zhVM_|oiU?C zhP$d4q@D`oKU$L0_oQf&BOhD3+Oy1n=P_zd^vXN4>hLrsmAcRjz)U^VCKEoWL5pgY zyC_>EVxL^S)Wr*v2Yrq?NlV865O61ob9Fzj(|dI5N*3)6$O*^tVakMYV=;|YM z?J90G0hzU6gay%*XD~H-AW+L|%oPL-T_&B!7K=vP@!@vkxpb`YMQ_=vz+}0kS!D3> z-1O_6;DIkZyy1A#mwx^wr-?wbx;yPBmCJ$0$ARxdC1cA&5}Mu99|x;rVb8{$aNnHA zziZEHHa{J@`LeF~Cl1e`ZOO~GUvVPO5) zJc)#1uoH7-KIKK95XeOPx!hG=Y1B-#_ z*O_b;o~%2qIVElnbB%jVwQbxf_zh8WBgY81CyyXnxs^J;Lun z*kG!$Hc4U*!8rp(Aw{M#jshK94)HyUTk|9`J_08;#ab*(#l&(AwLmI+if2h@sq<~? zx?pvd`_t5>$HIY$X$_5b$qKmdfvh@js;MW8{L^$*HQoM|v_#c>@SRaSL;3-gMz~of z`UxFg&*qao;)LE8A&G(?(CudV(wTy1^i)xJ4RwIiT-+4FF+ZAESq>}YE{wIFBGhcJ z`~d&u<<3kFTaCk|!E6GrMSYOPm}d@QaA!DUjtqG<=#k`F!W27Zc(qhwPSv2B`t54S zvN3f8j->~~sbwx^T!>yO=Gr{hyzVnYzWwkxEAv)aHFfd9^yr$-tjVCMkej$Y`LHH} zZgp6(Ne#(2B#aCi3x~*xQkphq{p-u^d6D%c4d;u{!l)TgudkdQC+h4~jLK{lk~`3q zt2N~YTbnZ>50r2WFJF34mc(4F11q#uSL%K+k-}aGeEBXw4L5?u7d>4IMdiUJ7S6$K` zF0^QBg(#EEkR5gl$`m`sP!yxGh9A_OVauj!J<0(nukG7%a>!Ekz>=mv1)H~7J!NUb ztJ3q-2?FaNjAdDcF9KGjWG<%s3e%d-p!G~B;cC8s1zvC_G=rhAbwJQhxPL&k*tOC& zc?0rn+fgphO_XK_udP>DzGt0U_f>aDkPG&-LL9u#2r2USknq5Ji=$X8Y`BHnCWtnvKxld;^rqJ^)EFR9`1}NEnp^fN6JPcznJ{?8^yhFzRQW`b{3i)urv$}#E0wF0K3j)U zlDg^*M#>E>tix7nX{z6q2H_kqw>_SHJ&md~ooQqqeKh}N^<-Zz$fy>+!L~Hiv#H1i zt9>|IbePyN!}-x8Xo{Dp_0txl9zakp0MBp*SsAZmt)fG_px@gA9JU3#YG)^rQ9cLQ zDo5R=(G6L70;0#_`kUUV*_6w5uGNN)%;2hqSBvQF2bD)L^6tioV*Gm*tuOgD-)A&LrA;q$p zn7~QFj2=;p(Mt{5DN#w+GC~t!Qto zz*oAr3yf3E)AsshqiAX0CX6!U*S#z!QqOSn=9n|_lROrNCB4xV?=(3bdF%UlMCo6= zGGek&)*s`?T)Bje?BnL!Nyfi znP0QhB(Bn3l?ZrN$I>v=R2t6ET2{?&7kI+JK2{)esl_);Q!fkLP5E7OGNpIJCI~ei zqQ!tU=vOx95+Er>HOX+2WuQqJTMO*v=Lc2+7ps3wbT8*cW)Mf@$!E;wVa6c24_Eft zqrZ){F@V@{c=`_gru!{7(zFM%JhZS(ejjklF!cHZT_cC7 z_Z+`~OR`W_h~uNueFYXzg3!3$NvdBOQ(SNNkLq=)4}LSz#+%{yzBVbzJI{GxvZ2ka z_FsKmjF$skH~jR4=whfT*Dnh4^Nj{PYv-C~2c3S!v!GK#l^W9t@t3;L9d^$tJ;f#9 zWrF03tA>2Rsz{EqMy>rOiV+Q)PDz0a>Y5A8h=b~D?MLHru%nLe7;a%R!6=5{Vg;$^ zg)1>^_bpEQXl(*L~5aAwd`B8tXx~Z2zblFUynnyG=llBsQ0e?-f%@D1KXpj& zw)bHG{;rn#VT0g`{yNxh5J{tl=9p=JBCyZ?Yw0Xnk(qLZR$ zWcRHb;5Q3=A>QM!x+=9HbPQ&KJ+-T((u7Lwf&`*~%l${+B<3{GmZJwvFES6PwmFxbGKkJWIrVp-224-;= z2UW>5cM!cx)VLT_v1gKWJeeb@dPK)B@WT^->|n5?zIfcyYwvBSF$achz<)m<%X0bX5b;$RA(yP^@e3Tzp}MvI)3dsI>%58!bNRhRidZ^XSc zvF3qf*Eu)AP}Z@!iU}tp>JGf;8qY+wkXyDETXUIH48UHmNBR-jI zj!cQ{rLFD!BhuV%2aT-Kyc3sF+~d5g(y|k_64H~h0#mjXiqg&#dlAOMg7loB$AUal zLuEusY)673%3XPuspJNeV#Jxcw33`Qg;IdWkD@P!ttlnX9jGP3H&ppwmKyy_+B*6b zi62vnL+_a65>^&O=Y~2;i!i1dgG8e#2Itf?!w$No(lbi;bG%|Q-h+ftq8AfPmP8I}mq~6*{l?R6r z$vRYOh`tuXB}Y`U^OyyRmP2yL2K!i_6_(NVLP7F#trnS|l^ zF(Ny#MaynIwm>t@%H#}ozPcq~=8kSWsKegt((BS`$DTWzOdX898jm!^`N1hbr zC2UR(!t+t-3lxDoQRwY~j|ihf-n`x-t8(w%l1>LQUsfg}H_y3m!Q7BfuWQxOS3-Cm z%MBW<6mw;!X4={WcKM^iFgK#LeXiqXn4bM^}P(e_BywmE&@3FN?pKd zbnn9G*UKbl7`s}R(5&a?o>5*yA3r{U+(vFy{J0lkou~2~!<9YL!R!~t%I@G!cw|l; zy3NLY%k9AYDwB zslj|!Q%x-N;UPEHrMcR=>-?%I4G=t>NeMA6L6OwBVZaUgtJ|EffyJrnvJACI?=K0A zuWmcvQXZDWyqefN zapuG1hoVNZW=Axd7GDWoWF?t3;-sJ1gSTHE0J^5z@zf1P&4hGO#vjbg0_&wJnNwy% zZv84ZITI%omzu=j%o_{m#tU_*ttuoc)Y{9lrIdq0sR>78scx>I{q4DNO6|E;+=!qc zE3Oi;N^|Y~V)kVfTRou8mEX^023X$Gv#*AI?xv07&0uf?lCqsiK}#w!Q>y?Ju5Am9fD~=SulW*RfEzlEg*}X=PpiuKf5eC7`lB69eUQ z3?8@x8moRhWkig9mYYb4ic?t9!j(l~P|H~OrF9~jYV}gLUItgS4=xb}84dCsSxeDhCc1)kT!Mmq9PB`hrEZ4E4qOm4)7|A)DEj1?tn z+cdXr+qP}n-urCZXWO=I+qP}nwr$LLJN@-^Ctqfg`87!;mDHcA%2Sn9tJZU0*W-qp zvP;3LCD=zsqO8p{i?pQg?OE$R#}|YD169;@?hGCu=HLzM!?auCm|BKK%N#;(xU7Q* zDcqZQjSJ8at?zFloyjJ!0K3OD zw71ec}r)jP%E*5UBZDmJMU~W65i!u13>*wk>N>C&V(;R4GNn^?gLyHJN z%wgR+HG|x`NFzpILk_x~R10ZlVjiKDF0w4#zSfN0c7QQ%Q%}}4i@YscqBAs`vQ@)? zt>OK2qX*{LI1sfe2>E(7GvN@MT~2z`?ABI?yZi6lh*iR0Ux&w(R1oO7ZpM4cB$FzahN2bo>I} z923+WKguZ3#}E^z3QUSOLYirG(`9C*v)!84W)&NQcxt}=C*`GzvqF?6@K%*;JdDZL z%DjHQeFIu-8+JnL{W%IdL+$eQc|X`E{ocAl7AoZV)rrp(_>I(xtL3y;QunTXR2!-F zc#4sqv?lPE@(p+!>X%3Eyi#zU8tqws%9HBa&3k3vsU5BG8FQ)9wBHeqQPqNee2{0& z1-o&NCHqDd;e=B+eh33ys~$7!@Xb6Id~*77gv*X(CM;ahjszu8B42Ze}%rNMlLYDb_RSTcc3(=wt>(ty!=K zBQzngC+PzDxpiCQ{f-3-ieWqxH90HwmDf~WIMv4K%+!l&vr=k)YyZ1pw7b^?oNI=t`uC6a!Snr6Gz48$9Fo~ zMbF0m^32C`B({=bVjp8T|LGzB?fBaHN`+#A(t_$yxuYon$9`X)5PPgtE83VTU= zxVg|zdo~->JY4Cu!I$0##CdeMU%k2F!yXOHUqJ^4*~A2o4SZeHBs^J zU%yg>NN^cy#8?FC$K*Gw&+zWejRSNma|c;Tb@`l?bAISb5Uy1VXDc_Tqp1mqW{iB) zU5%n_>P=KgR!-bViR`2L2lc3`8l2j~i<=4Qv+v$ppHbq=4$ z_4c)w(LTC24YWIo*$m{pNAsdiNbaioFJm z7gNf%S@RNwPL+y-c=Z%cHQDk7@0d|E=#+UAqjC6`C-0&k(1$ik#WusJ+`lJsKeoP?$7mR>-NK* zQESmor+3ZsHMkk=!R}XSNSzB+^S>q2PFJ3mRQl_#$6I`Bp52%9KXVHy`LE5Ed&P0G z`n&a7mC|U*P03CvWfN&(=Q1%^`>(-{1<#s=m*Po5rySBJ=E$9NheR1PGAZO%3DM&( z#_9|D;~|QmhxA4REQN&zu4ZD5r>7#Pv3R&$H1i7fnt>2(ZcsV{pn&2Wn0dVd_7iRX z6M(;pzxRcH%%j5qcn|pCIVsHmqQFR^$-v^@wd22s;;~4@N0;MW0S%IglNQQcjFu=Q zhnV~31ywu%NKgnn6NMb&kR(qqzJ`YG{twl)c;D0E7sao+9{Z_Q=v8`IA8x?Ab-(z( zY2C&TRadIKwB6;GsrQhX6*|SNxm}G0rwN}e-rv)tyBaPhHN&1}#bCJ-*XvzOzN->p z38A(Qti5h>=)M^6K>>- zT+Z|)i%k|7+!BDqRXku~X0$9@Fo<{J%usc(5zC=)-A|=SFOQ(*u3*0mZTmwNpTV*6 zhdzLIp9|!ACbH$MnNx7e1}0pg-0G3%NwcoLJ4edT>mP>R%{5fVRUxbmF6OwM=u^fBT{f6hpvKxU$i7VCGAgqWAJVZ%aJYhvh25$doLMtu^ zo!=lba`BCmL`P64W~{JC;Eg=|A|9# zrVv1_NXIC+2OV|Kv@gO#HK@E4f}`FyEusew`yh#?@GZ43_-wmIkX^$AP;?01GW4^7 z!@1C0*XZCd8UF1G`?=8kXDHvs_2Z>8z)qtM9(`WgChe>I6edHXkX4cL4mXq*FX(uE9c0_d1XB|#Q z#_?RXM$_D6B$J!i?;Iv|t`<`Yg)Tqf4r$N}ohV_GD>n+G>b?owO<($1p02nbFGwhk z(1%|gyzxTf89)mjsQ26@3jee0IX;&SXqoR4RT-+8(q-(KG3dltj7~B2K}Cwc;jchF z$sM#JZNcBMZRwYJKQe^G++;bD0{N401Q2lshH`+0;e)HEab^6UV|riif#uq5_I6K; zi$BBt)bA-vH%qMLqe|CaY`#8se{YUqec!*e@}3|{-Z*8N#@RIH8TVwm7ZI+gm_zR@ zxgZNof9FC4fW-7WAT8GT?b^)=N;XdyEqlBJmo*|2=@kEp z{xW8eW(+|dYP)$P6`SBZfLP8`WLZ;72a+>YyidY+ar;Vt&HVF&^?vlTD|-&3iXNDa z_TE1fP^;@T?w@+gdvm?r+uq@>n5y719~$HFZ9kYZW*|<>euDsK$6p=X;CpC0Ao;zL zkW2c_0N%y3#+a6mY@d+@N=9=*XAe|MjVK->W&a&WyjbS`qnjcYUekKFP(^B=}kj?07Q8Z!0J%FW^t9nl)7 z)w`O0oo``q7j8ywq&z%8j_yn@QChV*QQ43KO>4T4>v0=PqCY$e=loLUWa-Utk8=(@ z(NBhD7MW%-2`r7L0jTJY0!(ofhs6qAS>#F2pWvHGb5o&8vqe~M%wATOxVs&?>%KnE z>_e?!zk(0nbYtD8{=XETm8gu{TmRbX2{>yyo+{mf>W2QkP<};;eVXAoOi0WkVIr`V8sq02mhKhBxYnUo%;%x`DcYh7u3VMWc2fXmm>PgGX zfbBOP+`9cz(sVtD+;04mi3yp@=+pw@q6+9R3LOKadGLZ^I{Xd8lKM~>w)OuqMxiF) zGDsJ}08T_ihMG+J{pS5*JmD{SmNCSQNq~^Fe;~UDAIC4a0rZ_y-Jk4rHm>$Qcu*ah z%YT<_x~pAudk#+?4n=Y+$0qweo%XY@+Vuj~Gg9#Oqrqh>#RNKj(?n`}TIW-c%nNZL z>4Rp;b=OSyClhh}ZI;=mYCh@ouZL-diJ2v8mc3SS4i?9f1*dnovhUhgfWnQ210?*) zA@KmfmuLd!u#hF&aW3PT)3~|5w|%sIz4`a$1zmLMtP4RU;Yk_N?UMm!Co=JCT?ZNN zF-|`x%BBzPi^F9#O%Of!y;O^>>cf00l{&-C)ZVr}&Ar9r7=DwIAp`$+T#@IdAI1I- zqlKnv=-QuC-V{ZVdV1Ioz0{`)+6VrAX|7-uBdM+UC`%s7bw6x}>JpXJ?&VPd*#j z)xH$N)5rSa&h_Sd!Zp05ejuMqa1&3>NLIxt2xsHM87n1?dP%EU@>-!&Rd!p$d?hWK z46$-VCbvby%vK}JI4=KEL6nvun=I-dgM@^sg#~Q@mmREA4nZIe~UnHVm6m5>yBv$X84XxT!0; zeC4v>g|K_3PE)qjai=w99>jUn?K7+fQHZ6(b1T41WP)fpUNjzGlm!iP*kLLm4n3MKbw=JWN`GLN)glToibn-Z8I!DCIn<1PeK|JF_tT(j(^Ut z#EUA>6AYw;u)aoC3A|+CUY0ZFpBfS@$$bd4gl_2ZonWU98WK_)3W+wz==cSFG*~zA zGyBZgR#7fuy+{fv`I{D<`S{Ij>bEoweC7;#tYx+e$E1R|VHXYcDFGS+5jsclr7J~g zpmpgdKk?LwD4r||6qYdCG#uAeJ;U++&KTc4*+m1W{Co5km7@YddDi|LKp z8kG^*EA5x;@6TLoUY?u$-tI5*p2rmUp1S)<*hya}yNm9c)LP#=|K`WmgspVg_D4&t z8<{4u%q&L;`1}#`9@QzjD&*v|PHk0p%mgyDdNwdSaA}jQO283Ze=(V}^=>Dh!c^cE zT2;>Nq8}0hGi(wA+PDGIg|)-AleL?*O9d<3EJRd9=lLnlqCH9F)TCcHN;jPK4t=5U z2cey@O?UKF43Y3TLNN)zg+zcZQ+K*C#FM|JrBDeV0U-W!d?Hs`83z6|gE7ZC)w@-Z zY_6r+O-(F?paxy{T@lo@(}T#88x9IH+R( z32w6wVtnj#cdQ94U)d_NFH3PrEa|JH@4bKc(E~y*16@n_@Vr#rljY+B#MX3UxB}br zM9DRXQF9CD)Nd;9SwB>Ex0^9#F#}A7o;uU_d$=WTWJsfZHuVV__H3Os8f+}Obwu*&{I@1QzUn5;; zhD_YM%H~eqY6lH^LS%mL=68?($fP{Ax$snK`J-Q3LQh{*hjtaN-#50N=cmbekRq~8 zA#hY^42`?V=Q^z9?goa=(*IL-Mm&o<>wAWHhCQn^+bmnoOspA(1L;L1n=t`nG7O;* zGeHm596+B{{$S&#3rrJxLuV4B_|}9LT>b1x&Gt3v|!7q6%uoZAh{0?S6!pfu|X!({? zd)*x6#Kp8ih*D;RifNVyH(6aHT~A&09IR>T*~;8*W#ic-?G3UWQ%?RWR=vR-|SF3B3q zPGGZ@ddt33Ozuq7cFSRAU=-|#InG@8d)J4V1xPd3h^@gdLkB;>xM5FNw-jCTJGZKt zTAjkS6uY?yOk4!WjX_AVL5f5XAds>_wpbtmF(LBM`r;j2pz6bE-LzU`U4z^GBrjFa zs4G!TqU{;U^%6U!nLZ z?#XSdB31AZyf*5gn<{sLPie*(AW2YUFzCwi4Ptl{A;3wBpA^XwlqMmoNS!tRK7RCl zx8wglhShT;dltNyE$6idE%=q~{QP7_bmwrTdye$|{D6OBe+SZ?{FI15;^z-YD;f{z znxcZ#9*SjzpPIGBVfD`R8c^RhaKa8KsUO`F1!Er=;zS5)IVL47kalQ{LLL7uJ2JQ6 z#uR#JfDP2M(Pha}wJ-(Y&c-vX`vooe(oaK21j#?&3ppD5$(OGV7eV1cNkjxIo?kph zw2seIX=vc+OBRZbgkg8>uxhslu|y0^+&aNe(}*C_46*kfhv~zwdv4BRwa9=SE5|UU z9^k)69u7cq|0*qKcVE#7Te6zP&!|6WHB&NjZ1SF2QdgqS=C8KMy>Wb)y~kJ>Z5!ej zM@x$I-kpdxNNyTAXMxdTpkP-S9IR-f8l+*Fl>C67qycD|0*Xt~EJ@N_Yc)ahLq;kF z*hzH*^0Norr^Lk};t7V7w=cI+RW)jct6pJ2d`jv!9m7!?93&FU$g)>PG~yK8>2i`A z7nC(7FgO>X66bOFfjs7tm9F04bTe&jv2*pXZpX+B^xlRN-8X>-ybTr$YAeBw?zrw{hODY`$aTA7m#ni~@>5E%Bwo3a9@ zj{+ftj1r~@3=~hI-wZ5k8WljgR+VU?(Y2MGj_4oiwabR)GNAb}!M5)x~W1p(-6Cra>ndnLqEra}mC znGL=6>YE@Ed*yP2K^|tpRm$Wt4NlTbN2%PDzQS=Z7Sfju& zC%0~C8Uf7I=PXHU6VDNO0VS6l1~LGVngkwi5D`#M96Y%A0p0BJV0va;{rMV-NPQ1{ zRJwgC@=5$a>rGtks-$(Zw7DQJ?=4lLZ+;lb4s=ze-HUw?9n0Q~p}P(2Xh&`;uKWH> z3%QR@)EF(c2cs=JV<9ebRspsIm(CXrQXTtP4H32;q7o8Yu?F^&JQ&1m zu>jly=DwLY5b!@ zG#vzZ*$}E{Y3VhP9fGAAoR3z)d%@=1*`dx`)l-wB#IKnBkU>#fpDk~-;@h#3*uxf4 zX|*KQ{O9^Z-u0ewl&gQW{1Rs0o|ti+v}i5Ss)YRLsJW`Jm*tSC0d%K`_0<{J0T2~> z8Y1n}BGfAuV+|LU1KWlp+>LX34uCAmuB1#zXkXHn8@5=-`!#*ZKfJt~`}J`TX=k96 z{-iJ&KP$I{$JfWd<#thhxt_qbz_zNyH~b0E*EAN57*aUNf)vt0$j{JO=4_yhiP2Rx zB3;zWaw3@2yp~^z4D@c_mJ1(%q+-d8f#&EZ^n5s;0g)&sw{-wzi{*PLlB{4=u~;F$ zEN+?|z`6%8?gU$uiSNax2k_*_&Z~U+nFYMkOh?&=OH67`%S4QWH$mrjeEw= zH)Bcq8S?|kJJ_eNhMG}#h+4mwRj@@*1K?`m^@1x=ISR9}6t=I!?;s{@i`X&^CdmN;zX+yK^m}aM=jo5lu6ydk zt7EYRcLRV+QDSf#WW1lgPSVLRY3gyG)qtK&TZLP)pZi-#mNY;VKV zzF>krUUUYGFp6O`%KXJ^Q6HH8EVkcM7bK_MTI(sS-*wqIS_U0JX>~mdZvhz(D+hPm zKNEp_Wp`v(L_vFi?Md?XlNA3FH#8EVo1iebnFWKkwQv9spMBeYf*@8xu$!R50C=Op zLvJ6OKz8>E<9f_yVr;Mkob_O3?AIvA-_Jd2_sd&`w~(c1QNx8jYGYhFDbQhju;0P) z$98NCZ=jTSxNC^&B**QD7SGPE08dr)%=5=%5ho$#A}zW_1bzUw*@O_kO0rSCVl_;Z zq&$eA?iJ`g;kTSH8e|8FT}68#k3xVMX?!dIO#BB0sSM%=91kRbO-L~N5iD8eLX%aY zJyo;5{zr#;iXn8fnwc(6<$JRWI6DEz=>wff5*m*&)QPNP{Swp6)VeVeB5CpjIY7K{ zeg4DyqG?$QSq!{nrfVZ8g^WG-;Jl&mLcfVRTYHPYn~E~Wr{(lBZx@K@@-pZ@+1X#7 zr|?T^*FFudzY_E3I6=jG(zM%NJf3%!8m@&|*gYyOhDmnPss0vG(?{Jv3kHkUX;&dFO7}-LRmExng!9ttG zliE90F$Wrt#_#tRZrWYUH8Fn)i&_bNX%!I9~v~&oP$$e-8di7T*`Hl zqOGylIh80Ut7mK`ucuVAZ_01hZIzHGw zM%9*QY#FIRbQ#=Tl~9)up(Kf0UxZrVS||i8h^37%l`jPce6NVKTH2pgDvZ)XGlJ)U z*I=ol8Qlb7;hRpw4B|JJ<|Q{NrAS9z);O#*wunyocB#oR*K)(Qiz48wA^-I`a6!&W zTG>4Y@K8#*UD&xTh>~UBg_CvL9!toL*BAt+7=-#O(A-#piR+k%_ji@9g^c)Y!V*;% z*s2D?RKL|V@fAW1>^_HBMt-N-p(thPAP<=~B){xKW|~!*?&lBja~l|ZFU|^%+8lM0 zR<*YB*0#*0+V-}Dq7s(@zQH{5ioqvD30L!Czu+AV5K~f^X{NLVUBe+0?Of{gv{u3(mk>|# z&+_A<(yk;gJ1y(|YL}mU?s)Cqa4#G+e$K0<_^hMJSyur}!76=mN<{5pH3e|H0o>bI z!GOS1R0?kq+7-TsnFeD{vscjqOsLZ2vlf_zfn&>c9IaJ34<7-uV6o#5(y!_T{+0wS zE8FeYsI;4Rc%v9kxd5L<$FZ4eL`l-%*&gQ;PLSbFeI5wl`4fUM`7%@WioUK9$EVdB zqc~y8ey@Hx;^aG|VqAvb=ofITc879q7xIVIKhLzh{xo6c@uT`Hic~5=ZWKNG6c7Nu zM&p^F7zGwm7w-;gX#Y*+Gi1xE>)LC_Z4&&6vrmXTb?ovGmeF;6jfT9n_EgyMD)%Kj zVWb3B2<8I0m{@#vFNtzw++E-jQMykOA+Eu?mZ)`b5h4#$Ap>Kn(4kCXPa7F}0L!_Q zl~SPP1C=aT6~#PoM-2d^q!i!OGH@qvORXD#BH7mb3u9+J7NKD}C#>A!rv8Uv50M_| z$ik85Q0w68E%$-vpzFq|sr?1~6`W{suWug~Dev#zReYp4l!@^mT;nc^ESXv5bf#?n zoC7_Oy^|=1H93~VBRDMzZ6a-H@lT0kl>!9_@v0L!c2Fl_T6oiVo*;{?lhtUfWa$F_ zaneV8v7l90Z(@3qhHHRY+u7V=Jal}nGca(d))%us5ArQ2qfL zT-wCO?qJ}u8u#vn-MGiskjCozQ_5o6zyv0Qj_~5*R!Z_dBjf9$M~vmCbF!XFBleoAG^_9J40y{Z8i|k__tsC=Zxlh8c|fQfOoAm4{Iah} z01vGG4X?){Yj{R-qIGN(9s!$yNyy$*W%YSc0pP_zFeB$*1ZqL`%aGB6<1T`jqfish z@%l8onkQ8*R84s7+Nvn7f&1YeAUO_w3D%eQ_h1nR#8pO!4wwt-ajikz$ok_`0?mYj zl+&K2UTKZ}rxOG_acgvRd~}@qZuL{mJ!2r|xVnYaGR$y8+o04$*$h(?6jykPIo%#Z zfvmAN?l*LIn7rN{$d0p@w zFAXw$3lYYf-nFmB9hX*d5P0N7NgT39m`f5%r;HX+9C!6NM1IIAG7wJ!3FE;vu0`uH z!1!9LNQfQ-N)f@QPiHO_jEk}zi8-RK3nmB(sY3FpYpcgtS#-fUImz_Q>`9FN<^Jn{ zq8369)nP;CJ48&{#0z;K5YxK$Q_ZPgJCFktStJ7L1S~@vGrI&f3I~~nB1qscb06F4}zQ(+A@HlW``z@9D8Arg6(uk5w^T7oG2y_&kQahn$;6&q)nTB_3!f zHB&-P)8sQ|BBhoT88f@eO-r=BGcK53D<#^*UPt4`a2leplRSXyk8%KXk%DpqWXM;I zD%2&|Lhc1+830vXnui#99pUhmo*K#(U%W0}_gU^K^k$L!!pX+zd9+~6uSR1XsK6Rcm7iEED_%Jf4>J_HEoMIsr zSwu}1+r;z{sVM>uM(t@Sqs>}EOz!k0HZW5|)J5;q746z{qurQ>qKArVVY9dWG00RF zr1oj>0M`mZ7tZgZ$neJiU$;u~NNb76-vg8A9mlW1=5PZn>O=oAQQ<<284wp(=>_}{ zs%h`VIbex!R?IOw69YOwtvLO9e<9WGb^Pv1AiccxJpb|iT>+-=@m#$wS9H}`-a~q{ zI)%C>w~?Lf(Lut0Jhl~=zvXCvs>!Cu-Xm-gv{V=ol*~i{PJJI0WjwJ_f|l*-$2N+ zX#I-KdkGk1V6+wjiiOFu1Z~OX;w4lBSNTmy-V7SEpw;A+1jeVxf;Ki zRb(2*4!I0{7+J=50=eTQZ4wV%K(fmt*c@jc3*C=SMm7|**GD;ww;jW_rvUGFZQ0nP zy<|+Vt-7r0rVd1)rM*Mz)J6Uqvie{W0eG3Vfi_CslipW!EZVGrWaDAnWX#9frY;wX zO9-cL@RJlde*3Hf2?kav>i$w>*O7)JWkMey*wy1CJn`5=Ak_zFMMJNsL!{>=Re+kY zrIYJf2vVU_wDMTaRU&~MTwoex5vqTv{)6&obBe48T7A0Sc0OA1hu>nj9(1(dcD+8a zXQpm<_^e3F|9H+UucF*izMs@Ak<+_o9X(Uqej0jierPNCx*dg_oVyO^(sfcbWn6Oa z^f-8*s!sO&T(^Gtkoq2-ZP~o?HJP=P?6f*u2Wqod4TVB*=pyP*WahmiylUjYl&`}0 zF2R>VuPopdD5=SZ?=u*fRJ>_g*ykS`QA;J_C6wO~mgfj9ObyfdB>5rT^yuo@z;@Q^e<_*s4mPKhpH02!Cf#|^$z_>t_ijUF8At=__ z|EPZ_e|>*K_q4j5J|1XqXP(OQ`3iA!zFnN2^n4HOF)e65`YoL{2WsHM)v><@6`2qB z!Cw&q?zM@Em6Xo{SffMqah9_Hd<`a0qDu8$*4#qbqbZEnK}f740l)#mPoe=N_0Vg} zZHR$p`)@%FFzqk_fY2p=j?$DH0FlwUf(qrl`#40=P*$x~cU%#}KprImi#HiVIQuH{ zC9;e}>=SZJ2YX4lA*UKAQ#!=Qt;&|w2YiD=D3inWLE|)y+5&wSZK%! zEG$bJ)8XulX6?yiWhP=IpM?Qf!N88$EA@D{J96OT?Ye{!S=XEu?8phYWgn83?yCX< zG%e6zehF`$$e#k2J2B(<3`Zaha1MdBNWc%TxTODur_OX3XfvvH(Lf$+onVs8tndij zZ}@g9%bm>&U?3c1?~{Tk@)wzkdK>&ypOUsYK3wX~t?94~e%k0)|A1JdYwy~vkkBZS zL}F4O6aY#HSC=Up5I2ulGvS&b7el-hDl3h^5G}AM#)NQ zFkUJ3hta~hTgCP_&ttHs%3~s;r342cNRWqz)~^>^Ab3+2kwH7kiLdq+y?jf?g(5liHOKEj;6twSRAG`o|p!3gNEdl}6pVY>V6~P3gw4?7g@&l0; z=&5s71Q=^iQZE(l6GuBpO{pqk8NWSzp zvo~FfBP$UdSN1VLO>Ozi{RqLGD}^=T_29La%GM?ovX^~ePsK+h|(^VrkpaV~wpP`+7L@w=|6}tHLiy&7zr^GE-B> zqX}XE&Z_u&rYLrVB%ax#zj4emc<~bZS~q4HL{M7GM%To}{I9t)1pO-FMmH>+c@(o+ zkc$wk6c@Yf1bry92DE%Rg^EWpC ziMDRx`*+i+tfm{k7I*r8rgZa3{A6Hh;#e`ZD$neR)`b#!ybQS z;C@E1dC?`$>Dp)|a>ak_6)sI4CMWcRH;~_f>;6cBTIjEN9LamGH?ByKM}a-K8~qUd zBKCT$eRin3Ig5Ae?gCx|{vmv32GH&XlBiS{rrW_%>Yf<102Q)IP)b}I!GX(eeUQ}J zo34^Z>6l?DO*)dB_qzfgJWoLyW)!g(*AtuLC~pMVX+cYr!9_Hg=e)aaX>aX(?T}X-sFX5mdz3SK7_gR0PY&kO zML8{?tz?#_5Hn3dQnL({?T^uSy^~OY{-C3~RV1TMdF^Ym)7}R#?3EJc!67Et+KcS; z3IfUW)eE}(GL#0O_PI;kH!89KMa_aa*TpGfJ22>S9f_=W6VpSa=#pu_tKo4+c&Whj zilH{+>FIJk-Ig{bf#ca&XLsNJs7RTSZLRh_58Z8aaph_HcuZEGvZ3of&0p`1tcJWc zQI~mdrRTZBZCE`)B5Njd8>E-CTgwouMbF6BkU$aT1j6{SA?qd`aB3TLc%M|X6JMQG zs_BJ!WhM{TGu6&8K9Po$q#+I_;nX9BSe5Y@c_L!^iUvA&Mu`XT%l6orAggLjP%x2r z;}K%!ffWQn#E#0Fj02+{0|^2;eM@*XE?&W>7`a4vItM&Dkg~ov{5S#OBN9lDL+2w> zPU*)4!Gs^~`KH42 zQxB0Ry&8+;Z(xFeCadJf<-tY~W5A+;p@M0LYtaPQ%UW6o;A=@B@d|iQH5xL`4+
1Dvm>^3fCy4Q$aJo|lBt|rbomhcma08#`% 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 bk2yFx0Y2al*9kC;i;ErY-n~ciVrc&d3i$UZ literal 0 HcmV?d00001 diff --git a/doc/cheatsheet/Pandas_Cheat_Sheet_JA.pptx b/doc/cheatsheet/Pandas_Cheat_Sheet_JA.pptx new file mode 100644 index 0000000000000000000000000000000000000000..6270a71e20ee88f5020a76f139f404f18f4bfc30 GIT binary patch literal 76495 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

Ql*HwboAFVY{v2e%FIi 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 diff --git a/doc/cheatsheet/Pandas_Cheat_Sheet_JP.pptx b/doc/cheatsheet/Pandas_Cheat_Sheet_JP.pptx deleted file mode 100644 index f8b98a6f1f8e4afab4de9892cd8cd8920c6538f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 105265 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 z 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 F{U7 Date: Mon, 1 Oct 2018 23:52:55 +1200 Subject: [PATCH 46/85] STYLE: Added explicit exceptions (#22907) --- pandas/compat/pickle_compat.py | 8 ++++---- pandas/tseries/holiday.py | 6 +++--- pandas/util/_print_versions.py | 6 +++--- pandas/util/_validators.py | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pandas/compat/pickle_compat.py b/pandas/compat/pickle_compat.py index c1a9a9fc1ed131..713a5b1120bebe 100644 --- a/pandas/compat/pickle_compat.py +++ b/pandas/compat/pickle_compat.py @@ -33,7 +33,7 @@ def load_reduce(self): cls = args[0] stack[-1] = object.__new__(cls) return - except: + except TypeError: pass # try to re-encode the arguments @@ -44,7 +44,7 @@ def load_reduce(self): try: stack[-1] = func(*args) return - except: + except TypeError: pass # unknown exception, re-raise @@ -182,7 +182,7 @@ def load_newobj_ex(self): try: Unpickler.dispatch[pkl.NEWOBJ_EX[0]] = load_newobj_ex -except: +except (AttributeError, KeyError): pass @@ -210,5 +210,5 @@ def load(fh, encoding=None, compat=False, is_verbose=False): up.is_verbose = is_verbose return up.load() - except: + except (ValueError, TypeError): raise diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index b9c89c4e314f91..0497a827e2e1b2 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -294,7 +294,7 @@ def _apply_rule(self, dates): def register(cls): try: name = cls.name - except: + except AttributeError: name = cls.__name__ holiday_calendars[name] = cls @@ -426,7 +426,7 @@ def merge_class(base, other): """ try: other = other.rules - except: + except AttributeError: pass if not isinstance(other, list): @@ -435,7 +435,7 @@ def merge_class(base, other): try: base = base.rules - except: + except AttributeError: pass if not isinstance(base, list): diff --git a/pandas/util/_print_versions.py b/pandas/util/_print_versions.py index 5600834f3b6151..03fc82a3acef5b 100644 --- a/pandas/util/_print_versions.py +++ b/pandas/util/_print_versions.py @@ -21,7 +21,7 @@ def get_sys_info(): stdout=subprocess.PIPE, stderr=subprocess.PIPE) so, serr = pipe.communicate() - except: + except (OSError, ValueError): pass else: if pipe.returncode == 0: @@ -50,7 +50,7 @@ def get_sys_info(): ("LANG", "{lang}".format(lang=os.environ.get('LANG', "None"))), ("LOCALE", '.'.join(map(str, locale.getlocale()))), ]) - except: + except (KeyError, ValueError): pass return blob @@ -108,7 +108,7 @@ def show_versions(as_json=False): mod = importlib.import_module(modname) ver = ver_f(mod) deps_blob.append((modname, ver)) - except: + except ImportError: deps_blob.append((modname, None)) if (as_json): diff --git a/pandas/util/_validators.py b/pandas/util/_validators.py index a96563051e7de8..e51e0c88e5b95f 100644 --- a/pandas/util/_validators.py +++ b/pandas/util/_validators.py @@ -59,7 +59,7 @@ def _check_for_default_values(fname, arg_val_dict, compat_args): # could not compare them directly, so try comparison # using the 'is' operator - except: + except ValueError: match = (arg_val_dict[key] is compat_args[key]) if not match: From b92b0431bee1a3faa743aefbf72ec17ae1f80518 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 1 Oct 2018 08:08:59 -0400 Subject: [PATCH 47/85] Loc enhancements (#22826) --- asv_bench/benchmarks/indexing.py | 75 +++++++++++++++++++------------- doc/source/whatsnew/v0.24.0.txt | 2 + pandas/_libs/index.pyx | 25 +++++++---- 3 files changed, 64 insertions(+), 38 deletions(-) diff --git a/asv_bench/benchmarks/indexing.py b/asv_bench/benchmarks/indexing.py index 739ad6a3d278b7..c5b147b152aa6e 100644 --- a/asv_bench/benchmarks/indexing.py +++ b/asv_bench/benchmarks/indexing.py @@ -11,95 +11,110 @@ class NumericSeriesIndexing(object): goal_time = 0.2 - params = [Int64Index, Float64Index] - param = ['index'] + params = [ + (Int64Index, Float64Index), + ('unique_monotonic_inc', 'nonunique_monotonic_inc'), + ] + param_names = ['index_dtype', 'index_structure'] - def setup(self, index): + def setup(self, index, index_structure): N = 10**6 - idx = index(range(N)) - self.data = Series(np.random.rand(N), index=idx) + indices = { + 'unique_monotonic_inc': index(range(N)), + 'nonunique_monotonic_inc': index( + list(range(55)) + [54] + list(range(55, N - 1))), + } + self.data = Series(np.random.rand(N), index=indices[index_structure]) self.array = np.arange(10000) self.array_list = self.array.tolist() - def time_getitem_scalar(self, index): + def time_getitem_scalar(self, index, index_structure): self.data[800000] - def time_getitem_slice(self, index): + def time_getitem_slice(self, index, index_structure): self.data[:800000] - def time_getitem_list_like(self, index): + def time_getitem_list_like(self, index, index_structure): self.data[[800000]] - def time_getitem_array(self, index): + def time_getitem_array(self, index, index_structure): self.data[self.array] - def time_getitem_lists(self, index): + def time_getitem_lists(self, index, index_structure): self.data[self.array_list] - def time_iloc_array(self, index): + def time_iloc_array(self, index, index_structure): self.data.iloc[self.array] - def time_iloc_list_like(self, index): + def time_iloc_list_like(self, index, index_structure): self.data.iloc[[800000]] - def time_iloc_scalar(self, index): + def time_iloc_scalar(self, index, index_structure): self.data.iloc[800000] - def time_iloc_slice(self, index): + def time_iloc_slice(self, index, index_structure): self.data.iloc[:800000] - def time_ix_array(self, index): + def time_ix_array(self, index, index_structure): self.data.ix[self.array] - def time_ix_list_like(self, index): + def time_ix_list_like(self, index, index_structure): self.data.ix[[800000]] - def time_ix_scalar(self, index): + def time_ix_scalar(self, index, index_structure): self.data.ix[800000] - def time_ix_slice(self, index): + def time_ix_slice(self, index, index_structure): self.data.ix[:800000] - def time_loc_array(self, index): + def time_loc_array(self, index, index_structure): self.data.loc[self.array] - def time_loc_list_like(self, index): + def time_loc_list_like(self, index, index_structure): self.data.loc[[800000]] - def time_loc_scalar(self, index): + def time_loc_scalar(self, index, index_structure): self.data.loc[800000] - def time_loc_slice(self, index): + def time_loc_slice(self, index, index_structure): self.data.loc[:800000] class NonNumericSeriesIndexing(object): goal_time = 0.2 - params = ['string', 'datetime'] - param_names = ['index'] + params = [ + ('string', 'datetime'), + ('unique_monotonic_inc', 'nonunique_monotonic_inc'), + ] + param_names = ['index_dtype', 'index_structure'] - def setup(self, index): - N = 10**5 + def setup(self, index, index_structure): + N = 10**6 indexes = {'string': tm.makeStringIndex(N), 'datetime': date_range('1900', periods=N, freq='s')} index = indexes[index] + if index_structure == 'nonunique_monotonic_inc': + index = index.insert(item=index[2], loc=2)[:-1] self.s = Series(np.random.rand(N), index=index) self.lbl = index[80000] - def time_getitem_label_slice(self, index): + def time_getitem_label_slice(self, index, index_structure): self.s[:self.lbl] - def time_getitem_pos_slice(self, index): + def time_getitem_pos_slice(self, index, index_structure): self.s[:80000] - def time_get_value(self, index): + def time_get_value(self, index, index_structure): with warnings.catch_warnings(record=True): self.s.get_value(self.lbl) - def time_getitem_scalar(self, index): + def time_getitem_scalar(self, index, index_structure): self.s[self.lbl] + def time_getitem_list_like(self, index, index_structure): + self.s[[self.lbl]] + class DataFrameStringIndexing(object): diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 3e1711edb0f27c..6bb1ddfe2324dc 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -610,6 +610,8 @@ Performance Improvements :meth:`~HDFStore.keys`. (i.e. ``x in store`` checks are much faster) (:issue:`21372`) - Improved the performance of :func:`pandas.get_dummies` with ``sparse=True`` (:issue:`21997`) +- Improved performance of :func:`IndexEngine.get_indexer_non_unique` for sorted, non-unique indexes (:issue:`9466`) + .. _whatsnew_0240.docs: diff --git a/pandas/_libs/index.pyx b/pandas/_libs/index.pyx index 562c1ba2181411..3f76915655f580 100644 --- a/pandas/_libs/index.pyx +++ b/pandas/_libs/index.pyx @@ -294,14 +294,23 @@ cdef class IndexEngine: result = np.empty(n_alloc, dtype=np.int64) missing = np.empty(n_t, dtype=np.int64) - # form the set of the results (like ismember) - members = np.empty(n, dtype=np.uint8) - for i in range(n): - val = values[i] - if val in stargets: - if val not in d: - d[val] = [] - d[val].append(i) + # map each starget to its position in the index + if stargets and len(stargets) < 5 and self.is_monotonic_increasing: + # if there are few enough stargets and the index is monotonically + # increasing, then use binary search for each starget + for starget in stargets: + start = values.searchsorted(starget, side='left') + end = values.searchsorted(starget, side='right') + if start != end: + d[starget] = list(range(start, end)) + else: + # otherwise, map by iterating through all items in the index + for i in range(n): + val = values[i] + if val in stargets: + if val not in d: + d[val] = [] + d[val].append(i) for i in range(n_t): val = targets[i] From f021bbccd0d864b7276dc1a9c915b5ad45b5f6fd Mon Sep 17 00:00:00 2001 From: Stefano Miccoli Date: Mon, 1 Oct 2018 14:10:49 +0200 Subject: [PATCH 48/85] Fix Timestamp.round errors (#22802) --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/_libs/tslibs/timestamps.pyx | 137 +++++++++++++----- pandas/core/indexes/datetimelike.py | 12 +- .../indexes/datetimes/test_scalar_compat.py | 43 +++++- .../tests/scalar/timestamp/test_unary_ops.py | 43 +++++- 5 files changed, 192 insertions(+), 44 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 6bb1ddfe2324dc..5532771b38a0ee 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -654,6 +654,7 @@ Datetimelike - Bug in :class:`DatetimeIndex` subtraction that incorrectly failed to raise ``OverflowError`` (:issue:`22492`, :issue:`22508`) - Bug in :class:`DatetimeIndex` incorrectly allowing indexing with ``Timedelta`` object (:issue:`20464`) - Bug in :class:`DatetimeIndex` where frequency was being set if original frequency was ``None`` (:issue:`22150`) +- Bug in rounding methods of :class:`DatetimeIndex` (:meth:`~DatetimeIndex.round`, :meth:`~DatetimeIndex.ceil`, :meth:`~DatetimeIndex.floor`) and :class:`Timestamp` (:meth:`~Timestamp.round`, :meth:`~Timestamp.ceil`, :meth:`~Timestamp.floor`) could give rise to loss of precision (:issue:`22591`) Timedelta ^^^^^^^^^ diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index e985a519c3046d..0c2753dbc6f286 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -22,6 +22,7 @@ cimport ccalendar from conversion import tz_localize_to_utc, normalize_i8_timestamps from conversion cimport (tz_convert_single, _TSObject, convert_to_tsobject, convert_datetime_to_tsobject) +import enum from fields import get_start_end_field, get_date_name_field from nattype import NaT from nattype cimport NPY_NAT @@ -57,50 +58,114 @@ cdef inline object create_timestamp_from_ts(int64_t value, return ts_base -def round_ns(values, rounder, freq): +@enum.unique +class RoundTo(enum.Enum): """ - Applies rounding function at given frequency + enumeration defining the available rounding modes + + Attributes + ---------- + MINUS_INFTY + round towards -∞, or floor [2]_ + PLUS_INFTY + round towards +∞, or ceil [3]_ + NEAREST_HALF_EVEN + round to nearest, tie-break half to even [6]_ + NEAREST_HALF_MINUS_INFTY + round to nearest, tie-break half to -∞ [5]_ + NEAREST_HALF_PLUS_INFTY + round to nearest, tie-break half to +∞ [4]_ + + + References + ---------- + .. [1] "Rounding - Wikipedia" + https://en.wikipedia.org/wiki/Rounding + .. [2] "Rounding down" + https://en.wikipedia.org/wiki/Rounding#Rounding_down + .. [3] "Rounding up" + https://en.wikipedia.org/wiki/Rounding#Rounding_up + .. [4] "Round half up" + https://en.wikipedia.org/wiki/Rounding#Round_half_up + .. [5] "Round half down" + https://en.wikipedia.org/wiki/Rounding#Round_half_down + .. [6] "Round half to even" + https://en.wikipedia.org/wiki/Rounding#Round_half_to_even + """ + MINUS_INFTY = 0 + PLUS_INFTY = 1 + NEAREST_HALF_EVEN = 2 + NEAREST_HALF_PLUS_INFTY = 3 + NEAREST_HALF_MINUS_INFTY = 4 + + +cdef inline _npdivmod(x1, x2): + """implement divmod for numpy < 1.13""" + return np.floor_divide(x1, x2), np.remainder(x1, x2) + + +try: + from numpy import divmod as npdivmod +except ImportError: + npdivmod = _npdivmod + + +cdef inline _floor_int64(values, unit): + return values - np.remainder(values, unit) + +cdef inline _ceil_int64(values, unit): + return values + np.remainder(-values, unit) + +cdef inline _rounddown_int64(values, unit): + return _ceil_int64(values - unit//2, unit) + +cdef inline _roundup_int64(values, unit): + return _floor_int64(values + unit//2, unit) + + +def round_nsint64(values, mode, freq): + """ + Applies rounding mode at given frequency Parameters ---------- values : :obj:`ndarray` - rounder : function, eg. 'ceil', 'floor', 'round' + mode : instance of `RoundTo` enumeration freq : str, obj Returns ------- :obj:`ndarray` """ + + if not isinstance(mode, RoundTo): + raise ValueError('mode should be a RoundTo member') + unit = to_offset(freq).nanos - # GH21262 If the Timestamp is multiple of the freq str - # don't apply any rounding - mask = values % unit == 0 - if mask.all(): - return values - r = values.copy() - - if unit < 1000: - # for nano rounding, work with the last 6 digits separately - # due to float precision - buff = 1000000 - r[~mask] = (buff * (values[~mask] // buff) + - unit * (rounder((values[~mask] % buff) * - (1 / float(unit)))).astype('i8')) - else: - if unit % 1000 != 0: - msg = 'Precision will be lost using frequency: {}' - warnings.warn(msg.format(freq)) - # GH19206 - # to deal with round-off when unit is large - if unit >= 1e9: - divisor = 10 ** int(np.log10(unit / 1e7)) - else: - divisor = 10 - r[~mask] = (unit * rounder((values[~mask] * - (divisor / float(unit))) / divisor) - .astype('i8')) - return r + if mode is RoundTo.MINUS_INFTY: + return _floor_int64(values, unit) + elif mode is RoundTo.PLUS_INFTY: + return _ceil_int64(values, unit) + elif mode is RoundTo.NEAREST_HALF_MINUS_INFTY: + return _rounddown_int64(values, unit) + elif mode is RoundTo.NEAREST_HALF_PLUS_INFTY: + return _roundup_int64(values, unit) + elif mode is RoundTo.NEAREST_HALF_EVEN: + # for odd unit there is no need of a tie break + if unit % 2: + return _rounddown_int64(values, unit) + quotient, remainder = npdivmod(values, unit) + mask = np.logical_or( + remainder > (unit // 2), + np.logical_and(remainder == (unit // 2), quotient % 2) + ) + quotient[mask] += 1 + return quotient * unit + + # if/elif above should catch all rounding modes defined in enum 'RoundTo': + # if flow of control arrives here, it is a bug + assert False, "round_nsint64 called with an unrecognized rounding mode" # This is PITA. Because we inherit from datetime, which has very specific @@ -656,7 +721,7 @@ class Timestamp(_Timestamp): return create_timestamp_from_ts(ts.value, ts.dts, ts.tzinfo, freq) - def _round(self, freq, rounder, ambiguous='raise'): + def _round(self, freq, mode, ambiguous='raise'): if self.tz is not None: value = self.tz_localize(None).value else: @@ -665,7 +730,7 @@ class Timestamp(_Timestamp): value = np.array([value], dtype=np.int64) # Will only ever contain 1 element for timestamp - r = round_ns(value, rounder, freq)[0] + r = round_nsint64(value, mode, freq)[0] result = Timestamp(r, unit='ns') if self.tz is not None: result = result.tz_localize(self.tz, ambiguous=ambiguous) @@ -694,7 +759,7 @@ class Timestamp(_Timestamp): ------ ValueError if the freq cannot be converted """ - return self._round(freq, np.round, ambiguous) + return self._round(freq, RoundTo.NEAREST_HALF_EVEN, ambiguous) def floor(self, freq, ambiguous='raise'): """ @@ -715,7 +780,7 @@ class Timestamp(_Timestamp): ------ ValueError if the freq cannot be converted """ - return self._round(freq, np.floor, ambiguous) + return self._round(freq, RoundTo.MINUS_INFTY, ambiguous) def ceil(self, freq, ambiguous='raise'): """ @@ -736,7 +801,7 @@ class Timestamp(_Timestamp): ------ ValueError if the freq cannot be converted """ - return self._round(freq, np.ceil, ambiguous) + return self._round(freq, RoundTo.PLUS_INFTY, ambiguous) @property def tz(self): diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 578167a7db5009..f7f4f187f62027 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -11,7 +11,7 @@ import numpy as np from pandas._libs import lib, iNaT, NaT -from pandas._libs.tslibs.timestamps import round_ns +from pandas._libs.tslibs.timestamps import round_nsint64, RoundTo from pandas.core.dtypes.common import ( ensure_int64, @@ -180,10 +180,10 @@ class TimelikeOps(object): """ ) - def _round(self, freq, rounder, ambiguous): + def _round(self, freq, mode, ambiguous): # round the local times values = _ensure_datetimelike_to_i8(self) - result = round_ns(values, rounder, freq) + result = round_nsint64(values, mode, freq) result = self._maybe_mask_results(result, fill_value=NaT) attribs = self._get_attributes_dict() @@ -197,15 +197,15 @@ def _round(self, freq, rounder, ambiguous): @Appender((_round_doc + _round_example).format(op="round")) def round(self, freq, ambiguous='raise'): - return self._round(freq, np.round, ambiguous) + return self._round(freq, RoundTo.NEAREST_HALF_EVEN, ambiguous) @Appender((_round_doc + _floor_example).format(op="floor")) def floor(self, freq, ambiguous='raise'): - return self._round(freq, np.floor, ambiguous) + return self._round(freq, RoundTo.MINUS_INFTY, ambiguous) @Appender((_round_doc + _ceil_example).format(op="ceil")) def ceil(self, freq, ambiguous='raise'): - return self._round(freq, np.ceil, ambiguous) + return self._round(freq, RoundTo.PLUS_INFTY, ambiguous) class DatetimeIndexOpsMixin(DatetimeLikeArrayMixin): diff --git a/pandas/tests/indexes/datetimes/test_scalar_compat.py b/pandas/tests/indexes/datetimes/test_scalar_compat.py index 6f6f4eb8d24e37..d054121c6dfab0 100644 --- a/pandas/tests/indexes/datetimes/test_scalar_compat.py +++ b/pandas/tests/indexes/datetimes/test_scalar_compat.py @@ -11,6 +11,7 @@ import pandas as pd from pandas import date_range, Timestamp, DatetimeIndex +from pandas.tseries.frequencies import to_offset class TestDatetimeIndexOps(object): @@ -124,7 +125,7 @@ def test_round(self, tz_naive_fixture): expected = DatetimeIndex(['2016-10-17 12:00:00.001501030']) tm.assert_index_equal(result, expected) - with tm.assert_produces_warning(): + with tm.assert_produces_warning(False): ts = '2016-10-17 12:00:00.001501031' DatetimeIndex([ts]).round('1010ns') @@ -169,6 +170,46 @@ def test_ceil_floor_edge(self, test_input, rounder, freq, expected): 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 = DatetimeIndex(start=start, freq=index_freq, periods=periods) + 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(), "floor not a {} multiple".format(round_freq) + 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(), "ceil not a {} multiple".format(round_freq) + 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(), "round not a {} multiple".format(round_freq) + 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" + # ---------------------------------------------------------------- # DatetimeIndex.normalize diff --git a/pandas/tests/scalar/timestamp/test_unary_ops.py b/pandas/tests/scalar/timestamp/test_unary_ops.py index f83aa31edf95a1..b6c783dc07aecf 100644 --- a/pandas/tests/scalar/timestamp/test_unary_ops.py +++ b/pandas/tests/scalar/timestamp/test_unary_ops.py @@ -13,6 +13,7 @@ from pandas._libs.tslibs import conversion from pandas._libs.tslibs.frequencies import INVALID_FREQ_ERR_MSG from pandas import Timestamp, NaT +from pandas.tseries.frequencies import to_offset class TestTimestampUnaryOps(object): @@ -70,7 +71,7 @@ def test_round_subsecond(self): assert result == expected def test_round_nonstandard_freq(self): - with tm.assert_produces_warning(): + with tm.assert_produces_warning(False): Timestamp('2016-10-17 12:00:00.001501031').round('1010ns') def test_round_invalid_arg(self): @@ -154,6 +155,46 @@ def test_round_dst_border(self, method): with pytest.raises(pytz.AmbiguousTimeError): getattr(ts, method)('H', ambiguous='raise') + @pytest.mark.parametrize('timestamp', [ + '2018-01-01 0:0:0.124999360', + '2018-01-01 0:0:0.125000367', + '2018-01-01 0:0:0.125500', + '2018-01-01 0:0:0.126500', + '2018-01-01 12:00:00', + '2019-01-01 12:00:00', + ]) + @pytest.mark.parametrize('freq', [ + '2ns', '3ns', '4ns', '5ns', '6ns', '7ns', + '250ns', '500ns', '750ns', + '1us', '19us', '250us', '500us', '750us', + '1s', '2s', '3s', + '1D', + ]) + def test_round_int64(self, timestamp, freq): + """check that all rounding modes are accurate to int64 precision + see GH#22591 + """ + dt = Timestamp(timestamp) + unit = to_offset(freq).nanos + + # test floor + result = dt.floor(freq) + assert result.value % unit == 0, "floor not a {} multiple".format(freq) + assert 0 <= dt.value - result.value < unit, "floor error" + + # test ceil + result = dt.ceil(freq) + assert result.value % unit == 0, "ceil not a {} multiple".format(freq) + assert 0 <= result.value - dt.value < unit, "ceil error" + + # test round + result = dt.round(freq) + assert result.value % unit == 0, "round not a {} multiple".format(freq) + assert abs(result.value - dt.value) <= unit // 2, "round error" + if unit % 2 == 0 and abs(result.value - dt.value) == unit // 2: + # round half to even + assert result.value // unit % 2 == 0, "round half to even error" + # -------------------------------------------------------------- # Timestamp.replace From a277e4aec312abe78689ce361025cd60b25cf0c0 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Mon, 1 Oct 2018 05:12:35 -0700 Subject: [PATCH 49/85] BUG: Merge timezone aware data with DST (#22825) --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/core/indexes/datetimelike.py | 59 ++++++++++++++++-------- pandas/tests/indexing/test_coercion.py | 40 +++++++++++----- pandas/tests/reshape/merge/test_merge.py | 24 ++++++++++ 4 files changed, 93 insertions(+), 31 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 5532771b38a0ee..b71edcf1f6f516 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -815,6 +815,7 @@ Reshaping - Bug in :meth:`Series.replace` and meth:`DataFrame.replace` when dict is used as the ``to_replace`` value and one key in the dict is is another key's value, the results were inconsistent between using integer key and using string key (:issue:`20656`) - Bug in :meth:`DataFrame.drop_duplicates` for empty ``DataFrame`` which incorrectly raises an error (:issue:`20516`) - Bug in :func:`pandas.wide_to_long` when a string is passed to the stubnames argument and a column name is a substring of that stubname (:issue:`22468`) +- Bug in :func:`merge` when merging ``datetime64[ns, tz]`` data that contained a DST transition (:issue:`18885`) Build Changes ^^^^^^^^^^^^^ diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index f7f4f187f62027..37a12a588db035 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -277,7 +277,7 @@ def _evaluate_compare(self, other, op): except TypeError: return result - def _ensure_localized(self, result, ambiguous='raise'): + def _ensure_localized(self, arg, ambiguous='raise', from_utc=False): """ ensure that we are re-localized @@ -286,9 +286,11 @@ def _ensure_localized(self, result, ambiguous='raise'): Parameters ---------- - result : DatetimeIndex / i8 ndarray - ambiguous : str, bool, or bool-ndarray - default 'raise' + arg : DatetimeIndex / i8 ndarray + ambiguous : str, bool, or bool-ndarray, default 'raise' + from_utc : bool, default False + If True, localize the i8 ndarray to UTC first before converting to + the appropriate tz. If False, localize directly to the tz. Returns ------- @@ -297,10 +299,13 @@ def _ensure_localized(self, result, ambiguous='raise'): # reconvert to local tz if getattr(self, 'tz', None) is not None: - if not isinstance(result, ABCIndexClass): - result = self._simple_new(result) - result = result.tz_localize(self.tz, ambiguous=ambiguous) - return result + if not isinstance(arg, ABCIndexClass): + arg = self._simple_new(arg) + if from_utc: + arg = arg.tz_localize('UTC').tz_convert(self.tz) + else: + arg = arg.tz_localize(self.tz, ambiguous=ambiguous) + return arg def _box_values_as_index(self): """ @@ -622,11 +627,11 @@ def repeat(self, repeats, *args, **kwargs): @Appender(_index_shared_docs['where'] % _index_doc_kwargs) def where(self, cond, other=None): - other = _ensure_datetimelike_to_i8(other) - values = _ensure_datetimelike_to_i8(self) + other = _ensure_datetimelike_to_i8(other, to_utc=True) + values = _ensure_datetimelike_to_i8(self, to_utc=True) result = np.where(cond, values, other).astype('i8') - result = self._ensure_localized(result) + result = self._ensure_localized(result, from_utc=True) return self._shallow_copy(result, **self._get_attributes_dict()) @@ -695,23 +700,37 @@ def astype(self, dtype, copy=True): return super(DatetimeIndexOpsMixin, self).astype(dtype, copy=copy) -def _ensure_datetimelike_to_i8(other): - """ helper for coercing an input scalar or array to i8 """ +def _ensure_datetimelike_to_i8(other, to_utc=False): + """ + helper for coercing an input scalar or array to i8 + + Parameters + ---------- + other : 1d array + to_utc : bool, default False + If True, convert the values to UTC before extracting the i8 values + If False, extract the i8 values directly. + + Returns + ------- + i8 1d array + """ if is_scalar(other) and isna(other): - other = iNaT + return iNaT elif isinstance(other, ABCIndexClass): # convert tz if needed if getattr(other, 'tz', None) is not None: - other = other.tz_localize(None).asi8 - else: - other = other.asi8 + if to_utc: + other = other.tz_convert('UTC') + else: + other = other.tz_localize(None) else: try: - other = np.array(other, copy=False).view('i8') + return np.array(other, copy=False).view('i8') except TypeError: # period array cannot be coerces to int - other = Index(other).asi8 - return other + other = Index(other) + return other.asi8 def wrap_arithmetic_op(self, other, result): diff --git a/pandas/tests/indexing/test_coercion.py b/pandas/tests/indexing/test_coercion.py index e7daefffe5f6fe..2f44cb36eeb116 100644 --- a/pandas/tests/indexing/test_coercion.py +++ b/pandas/tests/indexing/test_coercion.py @@ -590,11 +590,9 @@ def test_where_series_datetime64(self, fill_val, exp_dtype): pd.Timestamp('2011-01-03'), values[3]]) self._assert_where_conversion(obj, cond, values, exp, exp_dtype) - @pytest.mark.parametrize("fill_val,exp_dtype", [ - (pd.Timestamp('2012-01-01'), 'datetime64[ns]'), - (pd.Timestamp('2012-01-01', tz='US/Eastern'), np.object)], - ids=['datetime64', 'datetime64tz']) - def test_where_index_datetime(self, fill_val, exp_dtype): + def test_where_index_datetime(self): + fill_val = pd.Timestamp('2012-01-01') + exp_dtype = 'datetime64[ns]' obj = pd.Index([pd.Timestamp('2011-01-01'), pd.Timestamp('2011-01-02'), pd.Timestamp('2011-01-03'), @@ -613,13 +611,33 @@ def test_where_index_datetime(self, fill_val, exp_dtype): pd.Timestamp('2011-01-03'), pd.Timestamp('2012-01-04')]) - if fill_val.tz: - self._assert_where_conversion(obj, cond, values, exp, - 'datetime64[ns]') - pytest.xfail("ToDo: do not ignore timezone, must be object") self._assert_where_conversion(obj, cond, values, exp, exp_dtype) - pytest.xfail("datetime64 + datetime64 -> datetime64 must support" - " scalar") + + @pytest.mark.xfail( + reason="GH 22839: do not ignore timezone, must be object") + def test_where_index_datetimetz(self): + fill_val = pd.Timestamp('2012-01-01', tz='US/Eastern') + exp_dtype = np.object + obj = pd.Index([pd.Timestamp('2011-01-01'), + pd.Timestamp('2011-01-02'), + pd.Timestamp('2011-01-03'), + pd.Timestamp('2011-01-04')]) + assert obj.dtype == 'datetime64[ns]' + cond = pd.Index([True, False, True, False]) + + msg = ("Index\\(\\.\\.\\.\\) must be called with a collection " + "of some kind") + with tm.assert_raises_regex(TypeError, msg): + obj.where(cond, fill_val) + + values = pd.Index(pd.date_range(fill_val, periods=4)) + exp = pd.Index([pd.Timestamp('2011-01-01'), + pd.Timestamp('2012-01-02', tz='US/Eastern'), + pd.Timestamp('2011-01-03'), + pd.Timestamp('2012-01-04', tz='US/Eastern')], + dtype=exp_dtype) + + self._assert_where_conversion(obj, cond, values, exp, exp_dtype) def test_where_index_complex128(self): pass diff --git a/pandas/tests/reshape/merge/test_merge.py b/pandas/tests/reshape/merge/test_merge.py index 42df4511578f1d..50ef622a4147f2 100644 --- a/pandas/tests/reshape/merge/test_merge.py +++ b/pandas/tests/reshape/merge/test_merge.py @@ -601,6 +601,30 @@ def test_merge_on_datetime64tz(self): assert result['value_x'].dtype == 'datetime64[ns, US/Eastern]' assert result['value_y'].dtype == 'datetime64[ns, US/Eastern]' + def test_merge_datetime64tz_with_dst_transition(self): + # GH 18885 + df1 = pd.DataFrame(pd.date_range( + '2017-10-29 01:00', periods=4, freq='H', tz='Europe/Madrid'), + columns=['date']) + df1['value'] = 1 + df2 = pd.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 = pd.merge(df1, df2, how='outer', on='date') + expected = pd.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 + }) + 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') From 5ce06b5bdb8c44043c6463bf8ce3da758800a189 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Mon, 1 Oct 2018 14:22:20 -0700 Subject: [PATCH 50/85] BUG: to_datetime preserves name of Index argument in the result (#22918) * BUG: to_datetime preserves name of Index argument in the result * correct test --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/core/tools/datetimes.py | 13 ++++++++----- pandas/tests/indexes/datetimes/test_tools.py | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index b71edcf1f6f516..851c1a3fbd6e9b 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -655,6 +655,7 @@ Datetimelike - Bug in :class:`DatetimeIndex` incorrectly allowing indexing with ``Timedelta`` object (:issue:`20464`) - Bug in :class:`DatetimeIndex` where frequency was being set if original frequency was ``None`` (:issue:`22150`) - Bug in rounding methods of :class:`DatetimeIndex` (:meth:`~DatetimeIndex.round`, :meth:`~DatetimeIndex.ceil`, :meth:`~DatetimeIndex.floor`) and :class:`Timestamp` (:meth:`~Timestamp.round`, :meth:`~Timestamp.ceil`, :meth:`~Timestamp.floor`) could give rise to loss of precision (:issue:`22591`) +- Bug in :func:`to_datetime` with an :class:`Index` argument that would drop the ``name`` from the result (:issue:`21697`) Timedelta ^^^^^^^^^ diff --git a/pandas/core/tools/datetimes.py b/pandas/core/tools/datetimes.py index 57387b9ea870a5..4a5290a90313d5 100644 --- a/pandas/core/tools/datetimes.py +++ b/pandas/core/tools/datetimes.py @@ -99,13 +99,13 @@ def _convert_and_box_cache(arg, cache_array, box, errors, name=None): result = Series(arg).map(cache_array) if box: if errors == 'ignore': - return Index(result) + return Index(result, name=name) else: return DatetimeIndex(result, name=name) return result.values -def _return_parsed_timezone_results(result, timezones, box, tz): +def _return_parsed_timezone_results(result, timezones, box, tz, name): """ Return results from array_strptime if a %z or %Z directive was passed. @@ -119,6 +119,9 @@ def _return_parsed_timezone_results(result, timezones, box, tz): True boxes result as an Index-like, False returns an ndarray tz : object None or pytz timezone object + name : string, default None + Name for a DatetimeIndex + Returns ------- tz_result : ndarray of parsed dates with timezone @@ -136,7 +139,7 @@ def _return_parsed_timezone_results(result, timezones, box, tz): in zip(result, timezones)]) if box: from pandas import Index - return Index(tz_results) + return Index(tz_results, name=name) return tz_results @@ -209,7 +212,7 @@ def _convert_listlike_datetimes(arg, box, format, name=None, tz=None, if box: if errors == 'ignore': from pandas import Index - return Index(result) + return Index(result, name=name) return DatetimeIndex(result, tz=tz, name=name) return result @@ -252,7 +255,7 @@ def _convert_listlike_datetimes(arg, box, format, name=None, tz=None, arg, format, exact=exact, errors=errors) if '%Z' in format or '%z' in format: return _return_parsed_timezone_results( - result, timezones, box, tz) + result, timezones, box, tz, name) except tslibs.OutOfBoundsDatetime: if errors == 'raise': raise diff --git a/pandas/tests/indexes/datetimes/test_tools.py b/pandas/tests/indexes/datetimes/test_tools.py index cc6db8f5854c81..3b7d6a709230bd 100644 --- a/pandas/tests/indexes/datetimes/test_tools.py +++ b/pandas/tests/indexes/datetimes/test_tools.py @@ -233,6 +233,15 @@ def test_to_datetime_parse_timezone_malformed(self, offset): with pytest.raises(ValueError): pd.to_datetime([date], format=fmt) + def test_to_datetime_parse_timezone_keeps_name(self): + # GH 21697 + fmt = '%Y-%m-%d %H:%M:%S %z' + arg = pd.Index(['2010-01-01 12:00:00 Z'], name='foo') + result = pd.to_datetime(arg, format=fmt) + expected = pd.DatetimeIndex(['2010-01-01 12:00:00'], tz='UTC', + name='foo') + tm.assert_index_equal(result, expected) + class TestToDatetime(object): def test_to_datetime_pydatetime(self): @@ -765,6 +774,14 @@ def test_unit_rounding(self, cache): expected = pd.Timestamp('2015-06-19 19:55:31.877000093') assert result == expected + @pytest.mark.parametrize('cache', [True, False]) + def test_unit_ignore_keeps_name(self, cache): + # GH 21697 + expected = pd.Index([15e9] * 2, name='name') + result = pd.to_datetime(expected, errors='ignore', box=True, unit='s', + cache=cache) + tm.assert_index_equal(result, expected) + @pytest.mark.parametrize('cache', [True, False]) def test_dataframe(self, cache): From 6247da0db4835ff723126640145b4fad3ce17343 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Tue, 2 Oct 2018 08:50:41 -0500 Subject: [PATCH 51/85] Provide default implementation for `data_repated` (#22935) --- pandas/tests/extension/conftest.py | 20 +++++++++++++++---- .../tests/extension/decimal/test_decimal.py | 8 -------- pandas/tests/extension/test_categorical.py | 9 --------- pandas/tests/extension/test_integer.py | 8 -------- pandas/tests/extension/test_interval.py | 9 --------- 5 files changed, 16 insertions(+), 38 deletions(-) diff --git a/pandas/tests/extension/conftest.py b/pandas/tests/extension/conftest.py index 4bbbb7df2f399a..8e397d228a5b6f 100644 --- a/pandas/tests/extension/conftest.py +++ b/pandas/tests/extension/conftest.py @@ -31,12 +31,24 @@ def all_data(request, data, data_missing): @pytest.fixture -def data_repeated(): - """Return different versions of data for count times""" +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 NotImplementedError - yield gen + yield data + return gen @pytest.fixture diff --git a/pandas/tests/extension/decimal/test_decimal.py b/pandas/tests/extension/decimal/test_decimal.py index 03fdd25826b799..93b8ea786ef5bc 100644 --- a/pandas/tests/extension/decimal/test_decimal.py +++ b/pandas/tests/extension/decimal/test_decimal.py @@ -30,14 +30,6 @@ def data_missing(): return DecimalArray([decimal.Decimal('NaN'), decimal.Decimal(1)]) -@pytest.fixture -def data_repeated(): - def gen(count): - for _ in range(count): - yield DecimalArray(make_data()) - yield gen - - @pytest.fixture def data_for_sorting(): return DecimalArray([decimal.Decimal('1'), diff --git a/pandas/tests/extension/test_categorical.py b/pandas/tests/extension/test_categorical.py index 6c6cf80c16da60..ff66f53eab6f6c 100644 --- a/pandas/tests/extension/test_categorical.py +++ b/pandas/tests/extension/test_categorical.py @@ -45,15 +45,6 @@ def data_missing(): return Categorical([np.nan, 'A']) -@pytest.fixture -def data_repeated(): - """Return different versions of data for count times""" - def gen(count): - for _ in range(count): - yield Categorical(make_data()) - yield gen - - @pytest.fixture def data_for_sorting(): return Categorical(['A', 'B', 'C'], categories=['C', 'A', 'B'], diff --git a/pandas/tests/extension/test_integer.py b/pandas/tests/extension/test_integer.py index 57e0922a0b7d97..7aa33006daddab 100644 --- a/pandas/tests/extension/test_integer.py +++ b/pandas/tests/extension/test_integer.py @@ -47,14 +47,6 @@ def data_missing(dtype): return integer_array([np.nan, 1], dtype=dtype) -@pytest.fixture -def data_repeated(data): - def gen(count): - for _ in range(count): - yield data - yield gen - - @pytest.fixture def data_for_sorting(dtype): return integer_array([1, 2, 0], dtype=dtype) diff --git a/pandas/tests/extension/test_interval.py b/pandas/tests/extension/test_interval.py index 34b98f590df0dc..7302c5757d1447 100644 --- a/pandas/tests/extension/test_interval.py +++ b/pandas/tests/extension/test_interval.py @@ -47,15 +47,6 @@ def data_missing(): return IntervalArray.from_tuples([None, (0, 1)]) -@pytest.fixture -def data_repeated(): - """Return different versions of data for count times""" - def gen(count): - for _ in range(count): - yield IntervalArray(make_data()) - yield gen - - @pytest.fixture def data_for_sorting(): return IntervalArray.from_tuples([(1, 2), (2, 3), (0, 1)]) From 1d9f76c5055d1ef31ce76134e88b5568a119f498 Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Tue, 2 Oct 2018 17:11:11 +0200 Subject: [PATCH 52/85] CLN: remove Index._to_embed (#22879) * CLN: remove Index._to_embed * pep8 --- pandas/core/indexes/base.py | 14 +------------- pandas/core/indexes/datetimes.py | 18 ++++-------------- pandas/core/indexes/period.py | 10 ---------- 3 files changed, 5 insertions(+), 37 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index b42bbdafcab458..af04a846ed787f 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1114,7 +1114,7 @@ def to_series(self, index=None, name=None): if name is None: name = self.name - return Series(self._to_embed(), index=index, name=name) + return Series(self.values.copy(), index=index, name=name) def to_frame(self, index=True, name=None): """ @@ -1177,18 +1177,6 @@ def to_frame(self, index=True, name=None): result.index = self return result - def _to_embed(self, keep_tz=False, dtype=None): - """ - *this is an internal non-public method* - - return an array repr of this object, potentially casting to object - - """ - if dtype is not None: - return self.astype(dtype)._to_embed(keep_tz=keep_tz) - - return self.values.copy() - _index_shared_docs['astype'] = """ Create an Index with values cast to dtypes. The class of a new Index is determined by dtype. When conversion is impossible, a ValueError diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 9b00f21668bf5b..a6cdaa0c2163a4 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -665,23 +665,13 @@ def to_series(self, keep_tz=False, index=None, name=None): if name is None: name = self.name - return Series(self._to_embed(keep_tz), index=index, name=name) - - def _to_embed(self, keep_tz=False, dtype=None): - """ - return an array repr of this object, potentially casting to object - - This is for internal compat - """ - if dtype is not None: - return self.astype(dtype)._to_embed(keep_tz=keep_tz) - if keep_tz and self.tz is not None: - # preserve the tz & copy - return self.copy(deep=True) + values = self.copy(deep=True) + else: + values = self.values.copy() - return self.values.copy() + return Series(values, index=index, name=name) def to_period(self, freq=None): """ diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 0f86e18103e3ce..969391569ce503 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -365,16 +365,6 @@ def __array_wrap__(self, result, context=None): # cannot pass _simple_new as it is return self._shallow_copy(result, freq=self.freq, name=self.name) - def _to_embed(self, keep_tz=False, dtype=None): - """ - return an array repr of this object, potentially casting to object - """ - - if dtype is not None: - return self.astype(dtype)._to_embed(keep_tz=keep_tz) - - return self.astype(object).values - @property def size(self): # Avoid materializing self._values From 9caf04836ad34ca17da7b86ba7120cca58ce142a Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Tue, 2 Oct 2018 13:25:22 -0500 Subject: [PATCH 53/85] CI: change windows vm image (#22948) --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c82dafa2249613..5d473bfc5a38c7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -18,8 +18,8 @@ jobs: - template: ci/azure/windows.yml parameters: name: Windows - vmImage: vs2017-win2017 + vmImage: vs2017-win2016 - template: ci/azure/windows-py27.yml parameters: name: WindowsPy27 - vmImage: vs2017-win2017 + vmImage: vs2017-win2016 From 1102a33d9776ed316cade079e22be6daa76c9e42 Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Tue, 2 Oct 2018 22:31:36 +0200 Subject: [PATCH 54/85] DOC/CLN: clean-up shared_docs in generic.py (#20074) --- pandas/core/frame.py | 9 +++-- pandas/core/generic.py | 65 ++++++++++-------------------------- pandas/core/panel.py | 16 +++++++-- pandas/core/series.py | 5 +-- pandas/core/sparse/series.py | 7 ++-- 5 files changed, 44 insertions(+), 58 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index b4e8b4e3a6bec5..15cebb88faea73 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -3629,7 +3629,8 @@ def align(self, other, join='outer', axis=None, level=None, copy=True, fill_axis=fill_axis, broadcast_axis=broadcast_axis) - @Appender(_shared_docs['reindex'] % _shared_doc_kwargs) + @Substitution(**_shared_doc_kwargs) + @Appender(NDFrame.reindex.__doc__) @rewrite_axis_style_signature('labels', [('method', None), ('copy', True), ('level', None), @@ -4479,7 +4480,8 @@ def f(vals): # ---------------------------------------------------------------------- # Sorting - @Appender(_shared_docs['sort_values'] % _shared_doc_kwargs) + @Substitution(**_shared_doc_kwargs) + @Appender(NDFrame.sort_values.__doc__) def sort_values(self, by, axis=0, ascending=True, inplace=False, kind='quicksort', na_position='last'): inplace = validate_bool_kwarg(inplace, 'inplace') @@ -4521,7 +4523,8 @@ def sort_values(self, by, axis=0, ascending=True, inplace=False, else: return self._constructor(new_data).__finalize__(self) - @Appender(_shared_docs['sort_index'] % _shared_doc_kwargs) + @Substitution(**_shared_doc_kwargs) + @Appender(NDFrame.sort_index.__doc__) def sort_index(self, axis=0, level=None, ascending=True, inplace=False, kind='quicksort', na_position='last', sort_remaining=True, by=None): diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 393e7caae5fabb..8fed92f7ed6b96 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -643,7 +643,8 @@ def _set_axis(self, axis, labels): self._data.set_axis(axis, labels) self._clear_item_cache() - _shared_docs['transpose'] = """ + def transpose(self, *args, **kwargs): + """ Permute the dimensions of the %(klass)s Parameters @@ -663,9 +664,6 @@ def _set_axis(self, axis, labels): y : same as input """ - @Appender(_shared_docs['transpose'] % _shared_doc_kwargs) - def transpose(self, *args, **kwargs): - # construct the args axes, kwargs = self._construct_axes_from_arguments(args, kwargs, require_all=True) @@ -965,9 +963,8 @@ def swaplevel(self, i=-2, j=-1, axis=0): # ---------------------------------------------------------------------- # Rename - # TODO: define separate funcs for DataFrame, Series and Panel so you can - # get completion on keyword arguments. - _shared_docs['rename'] = """ + def rename(self, *args, **kwargs): + """ Alter axes input function or functions. Function / dict values must be unique (1-to-1). Labels not contained in a dict / Series will be left as-is. Extra labels listed don't throw an error. Alternatively, change @@ -975,13 +972,11 @@ def swaplevel(self, i=-2, j=-1, axis=0): Parameters ---------- - %(optional_mapper)s %(axes)s : scalar, list-like, dict-like or function, optional Scalar or list-like will alter the ``Series.name`` attribute, and raise on DataFrame or Panel. dict-like or functions are transformations to apply to that axis' values - %(optional_axis)s copy : boolean, default True Also copy underlying data inplace : boolean, default False @@ -1069,12 +1064,6 @@ def swaplevel(self, i=-2, j=-1, axis=0): See the :ref:`user guide ` for more. """ - - @Appender(_shared_docs['rename'] % dict(axes='axes keywords for this' - ' object', klass='NDFrame', - optional_mapper='', - optional_axis='')) - def rename(self, *args, **kwargs): axes, kwargs = self._construct_axes_from_arguments(args, kwargs) copy = kwargs.pop('copy', True) inplace = kwargs.pop('inplace', False) @@ -1127,8 +1116,6 @@ def f(x): else: return result.__finalize__(self) - rename.__doc__ = _shared_docs['rename'] - def rename_axis(self, mapper, axis=0, copy=True, inplace=False): """ Alter the name of the index or columns. @@ -3024,7 +3011,8 @@ def __delitem__(self, key): except KeyError: pass - _shared_docs['_take'] = """ + def _take(self, indices, axis=0, is_copy=True): + """ Return the elements in the given *positional* indices along an axis. This means that we are not indexing according to actual values in @@ -3055,9 +3043,6 @@ def __delitem__(self, key): numpy.ndarray.take numpy.take """ - - @Appender(_shared_docs['_take']) - def _take(self, indices, axis=0, is_copy=True): self._consolidate_inplace() new_data = self._data.take(indices, @@ -3072,7 +3057,8 @@ def _take(self, indices, axis=0, is_copy=True): return result - _shared_docs['take'] = """ + def take(self, indices, axis=0, convert=None, is_copy=True, **kwargs): + """ Return the elements in the given *positional* indices along an axis. This means that we are not indexing according to actual values in @@ -3155,9 +3141,6 @@ class max_speed 1 monkey mammal NaN 3 lion mammal 80.5 """ - - @Appender(_shared_docs['take']) - def take(self, indices, axis=0, convert=None, is_copy=True, **kwargs): if convert is not None: msg = ("The 'convert' parameter is deprecated " "and will be removed in a future version.") @@ -3580,7 +3563,9 @@ def add_suffix(self, suffix): mapper = {self._info_axis_name: f} return self.rename(**mapper) - _shared_docs['sort_values'] = """ + def sort_values(self, by=None, axis=0, ascending=True, inplace=False, + kind='quicksort', na_position='last'): + """ Sort by the values along either axis Parameters @@ -3665,17 +3650,12 @@ def add_suffix(self, suffix): 0 A 2 0 1 A 1 1 """ - - def sort_values(self, by=None, axis=0, ascending=True, inplace=False, - kind='quicksort', na_position='last'): - """ - NOT IMPLEMENTED: do not call this method, as sorting values is not - supported for Panel objects and will raise an error. - """ raise NotImplementedError("sort_values has not been implemented " "on Panel or Panel4D objects.") - _shared_docs['sort_index'] = """ + def sort_index(self, axis=0, level=None, ascending=True, inplace=False, + kind='quicksort', na_position='last', sort_remaining=True): + """ Sort object by labels (along an axis) Parameters @@ -3703,10 +3683,6 @@ def sort_values(self, by=None, axis=0, ascending=True, inplace=False, ------- sorted_obj : %(klass)s """ - - @Appender(_shared_docs['sort_index'] % dict(axes="axes", klass="NDFrame")) - def sort_index(self, axis=0, level=None, ascending=True, inplace=False, - kind='quicksort', na_position='last', sort_remaining=True): inplace = validate_bool_kwarg(inplace, 'inplace') axis = self._get_axis_number(axis) axis_name = self._get_axis_name(axis) @@ -3724,7 +3700,8 @@ def sort_index(self, axis=0, level=None, ascending=True, inplace=False, new_axis = labels.take(sort_index) return self.reindex(**{axis_name: new_axis}) - _shared_docs['reindex'] = """ + def reindex(self, *args, **kwargs): + """ Conform %(klass)s to new index with optional filling logic, placing NA/NaN in locations having no value in the previous index. A new object is produced unless the new index is equivalent to the current one and @@ -3920,14 +3897,8 @@ def sort_index(self, axis=0, level=None, ascending=True, inplace=False, ------- reindexed : %(klass)s """ - - # TODO: Decide if we care about having different examples for different - # kinds - - @Appender(_shared_docs['reindex'] % dict(axes="axes", klass="NDFrame", - optional_labels="", - optional_axis="")) - def reindex(self, *args, **kwargs): + # TODO: Decide if we care about having different examples for different + # kinds # construct the args axes, kwargs = self._construct_axes_from_arguments(args, kwargs) diff --git a/pandas/core/panel.py b/pandas/core/panel.py index 81d1e83ee68708..1e2d4000413bba 100644 --- a/pandas/core/panel.py +++ b/pandas/core/panel.py @@ -1215,7 +1215,8 @@ def _wrap_result(self, result, axis): return self._construct_return_type(result, axes) - @Appender(_shared_docs['reindex'] % _shared_doc_kwargs) + @Substitution(**_shared_doc_kwargs) + @Appender(NDFrame.reindex.__doc__) def reindex(self, *args, **kwargs): major = kwargs.pop("major", None) minor = kwargs.pop('minor', None) @@ -1236,7 +1237,8 @@ def reindex(self, *args, **kwargs): kwargs.pop('labels', None) return super(Panel, self).reindex(**kwargs) - @Appender(_shared_docs['rename'] % _shared_doc_kwargs) + @Substitution(**_shared_doc_kwargs) + @Appender(NDFrame.rename.__doc__) def rename(self, items=None, major_axis=None, minor_axis=None, **kwargs): major_axis = (major_axis if major_axis is not None else kwargs.pop('major', None)) @@ -1253,7 +1255,8 @@ def reindex_axis(self, labels, axis=0, method=None, level=None, copy=True, copy=copy, limit=limit, fill_value=fill_value) - @Appender(_shared_docs['transpose'] % _shared_doc_kwargs) + @Substitution(**_shared_doc_kwargs) + @Appender(NDFrame.transpose.__doc__) def transpose(self, *args, **kwargs): # check if a list of axes was passed in instead as a # single *args element @@ -1536,6 +1539,13 @@ def _extract_axis(self, data, axis=0, intersect=False): return ensure_index(index) + def sort_values(self, *args, **kwargs): + """ + NOT IMPLEMENTED: do not call this method, as sorting values is not + supported for Panel objects and will raise an error. + """ + super(Panel, self).sort_values(*args, **kwargs) + Panel._setup_axes(axes=['items', 'major_axis', 'minor_axis'], info_axis=0, stat_axis=1, aliases={'major': 'major_axis', diff --git a/pandas/core/series.py b/pandas/core/series.py index 83f80c305c5ebf..82198c2b3edd5e 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -3496,7 +3496,8 @@ def rename(self, index=None, **kwargs): return self._set_name(index, inplace=kwargs.get('inplace')) return super(Series, self).rename(index=index, **kwargs) - @Appender(generic._shared_docs['reindex'] % _shared_doc_kwargs) + @Substitution(**_shared_doc_kwargs) + @Appender(generic.NDFrame.reindex.__doc__) def reindex(self, index=None, **kwargs): return super(Series, self).reindex(index=index, **kwargs) @@ -3680,7 +3681,7 @@ def memory_usage(self, index=True, deep=False): v += self.index.memory_usage(deep=deep) return v - @Appender(generic._shared_docs['_take']) + @Appender(generic.NDFrame._take.__doc__) def _take(self, indices, axis=0, is_copy=False): indices = ensure_platform_int(indices) diff --git a/pandas/core/sparse/series.py b/pandas/core/sparse/series.py index 8ac5d81f23bb20..97cd3a0a1fb6ab 100644 --- a/pandas/core/sparse/series.py +++ b/pandas/core/sparse/series.py @@ -19,7 +19,7 @@ import pandas.core.indexes.base as ibase import pandas.core.ops as ops import pandas._libs.index as libindex -from pandas.util._decorators import Appender +from pandas.util._decorators import Appender, Substitution from pandas.core.sparse.array import ( make_sparse, SparseArray, @@ -563,7 +563,8 @@ def copy(self, deep=True): return self._constructor(new_data, sparse_index=self.sp_index, fill_value=self.fill_value).__finalize__(self) - @Appender(generic._shared_docs['reindex'] % _shared_doc_kwargs) + @Substitution(**_shared_doc_kwargs) + @Appender(generic.NDFrame.reindex.__doc__) def reindex(self, index=None, method=None, copy=True, limit=None, **kwargs): @@ -592,7 +593,7 @@ def sparse_reindex(self, new_index): sparse_index=new_index, fill_value=self.fill_value).__finalize__(self) - @Appender(generic._shared_docs['take']) + @Appender(generic.NDFrame.take.__doc__) def take(self, indices, axis=0, convert=None, *args, **kwargs): if convert is not None: msg = ("The 'convert' parameter is deprecated " From 8e749a33b5f814bded42044a4182449d5d6c8213 Mon Sep 17 00:00:00 2001 From: Pamela Wu Date: Tue, 2 Oct 2018 17:14:48 -0400 Subject: [PATCH 55/85] CLN GH22874 replace bare excepts in pandas/io/pytables.py (#22919) --- pandas/io/pytables.py | 49 ++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/pandas/io/pytables.py b/pandas/io/pytables.py index c57b1c3e211f66..fc9e415ed38f7a 100644 --- a/pandas/io/pytables.py +++ b/pandas/io/pytables.py @@ -258,7 +258,7 @@ def _tables(): try: _table_file_open_policy_is_strict = ( tables.file._FILE_OPEN_POLICY == 'strict') - except: + except AttributeError: pass return _table_mod @@ -395,11 +395,11 @@ def read_hdf(path_or_buf, key=None, mode='r', **kwargs): 'contains multiple datasets.') key = candidate_only_group._v_pathname return store.select(key, auto_close=auto_close, **kwargs) - except: + except (ValueError, TypeError): # if there is an error, close the store try: store.close() - except: + except AttributeError: pass raise @@ -517,7 +517,7 @@ def __getattr__(self, name): """ allow attribute access to get stores """ try: return self.get(name) - except: + except (KeyError, ClosedFileError): pass raise AttributeError("'%s' object has no attribute '%s'" % (type(self).__name__, name)) @@ -675,7 +675,7 @@ def flush(self, fsync=False): if fsync: try: os.fsync(self._handle.fileno()) - except: + except OSError: pass def get(self, key): @@ -1161,7 +1161,7 @@ def get_node(self, key): if not key.startswith('/'): key = '/' + key return self._handle.get_node(self.root, key) - except: + except _table_mod.exceptions.NoSuchNodeError: return None def get_storer(self, key): @@ -1270,7 +1270,7 @@ def _validate_format(self, format, kwargs): # validate try: kwargs['format'] = _FORMAT_MAP[format.lower()] - except: + except KeyError: raise TypeError("invalid HDFStore format specified [{0}]" .format(format)) @@ -1307,7 +1307,7 @@ def error(t): try: pt = _TYPE_MAP[type(value)] - except: + except KeyError: error('_TYPE_MAP') # we are actually a table @@ -1318,7 +1318,7 @@ def error(t): if u('table') not in pt: try: return globals()[_STORER_MAP[pt]](self, group, **kwargs) - except: + except KeyError: error('_STORER_MAP') # existing node (and must be a table) @@ -1354,12 +1354,12 @@ def error(t): fields = group.table._v_attrs.fields if len(fields) == 1 and fields[0] == u('value'): tt = u('legacy_frame') - except: + except IndexError: pass try: return globals()[_TABLE_MAP[tt]](self, group, **kwargs) - except: + except KeyError: error('_TABLE_MAP') def _write_to_group(self, key, value, format, index=True, append=False, @@ -1624,7 +1624,7 @@ def is_indexed(self): """ return whether I am an indexed column """ try: return getattr(self.table.cols, self.cname).is_indexed - except: + except AttributeError: False def copy(self): @@ -1654,9 +1654,10 @@ def convert(self, values, nan_rep, encoding, errors): kwargs['freq'] = _ensure_decoded(self.freq) if self.index_name is not None: kwargs['name'] = _ensure_decoded(self.index_name) + # making an Index instance could throw a number of different errors try: self.values = Index(values, **kwargs) - except: + except Exception: # noqa: E722 # if the output freq is different that what we recorded, # it should be None (see also 'doc example part 2') @@ -1869,7 +1870,7 @@ def create_for_block( m = re.search(r"values_block_(\d+)", name) if m: name = "values_%s" % m.groups()[0] - except: + except IndexError: pass return cls(name=name, cname=cname, **kwargs) @@ -2232,7 +2233,7 @@ def convert(self, values, nan_rep, encoding, errors): try: self.data = self.data.astype(dtype, copy=False) - except: + except TypeError: self.data = self.data.astype('O', copy=False) # convert nans / decode @@ -2325,7 +2326,7 @@ def set_version(self): self.version = tuple(int(x) for x in version.split('.')) if len(self.version) == 2: self.version = self.version + (0,) - except: + except AttributeError: self.version = (0, 0, 0) @property @@ -2769,7 +2770,7 @@ def write_array(self, key, value, items=None): else: try: items = list(items) - except: + except TypeError: pass ws = performance_doc % (inferred_type, key, items) warnings.warn(ws, PerformanceWarning, stacklevel=7) @@ -2843,7 +2844,7 @@ class SeriesFixed(GenericFixed): def shape(self): try: return len(getattr(self.group, 'values')), - except: + except (TypeError, AttributeError): return None def read(self, **kwargs): @@ -2961,7 +2962,7 @@ def shape(self): shape = shape[::-1] return shape - except: + except AttributeError: return None def read(self, start=None, stop=None, **kwargs): @@ -3495,7 +3496,7 @@ def create_axes(self, axes, obj, validate=True, nan_rep=None, if axes is None: try: axes = _AXES_MAP[type(obj)] - except: + except KeyError: raise TypeError("cannot properly create the storer for: " "[group->%s,value->%s]" % (self.group._v_name, type(obj))) @@ -3614,7 +3615,7 @@ def get_blk_items(mgr, blocks): b, b_items = by_items.pop(items) new_blocks.append(b) new_blk_items.append(b_items) - except: + except (IndexError, KeyError): raise ValueError( "cannot match existing table structure for [%s] on " "appending data" % ','.join(pprint_thing(item) for @@ -3642,7 +3643,7 @@ def get_blk_items(mgr, blocks): if existing_table is not None and validate: try: existing_col = existing_table.values_axes[i] - except: + except (IndexError, KeyError): raise ValueError("Incompatible appended table [%s] with " "existing table [%s]" % (blocks, existing_table.values_axes)) @@ -4460,7 +4461,7 @@ def _get_info(info, name): """ get/create the info for this name """ try: idx = info[name] - except: + except KeyError: idx = info[name] = dict() return idx @@ -4782,7 +4783,7 @@ def __init__(self, table, where=None, start=None, stop=None, **kwargs): ) self.coordinates = where - except: + except ValueError: pass if self.coordinates is None: From c44bad24996f9e747f2119fa0c6a90d893f6e2aa Mon Sep 17 00:00:00 2001 From: Pamela Wu Date: Tue, 2 Oct 2018 17:16:25 -0400 Subject: [PATCH 56/85] CLN GH22873 Replace base excepts in pandas/core (#22901) --- doc/source/whatsnew/v0.24.0.txt | 1 - pandas/core/computation/pytables.py | 2 +- pandas/core/dtypes/common.py | 2 +- pandas/core/dtypes/dtypes.py | 8 ++++---- pandas/core/frame.py | 4 ++-- pandas/core/indexes/frozen.py | 2 +- pandas/core/indexes/multi.py | 12 +++++++----- pandas/core/indexing.py | 6 +++--- pandas/core/internals/blocks.py | 10 +++++----- pandas/core/nanops.py | 5 +++-- pandas/core/ops.py | 3 ++- pandas/core/sparse/array.py | 2 +- pandas/core/tools/datetimes.py | 10 +++++----- pandas/core/window.py | 2 +- 14 files changed, 36 insertions(+), 33 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 851c1a3fbd6e9b..f83185173c3e31 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -834,4 +834,3 @@ Other - :meth:`DataFrame.nlargest` and :meth:`DataFrame.nsmallest` now returns the correct n values when keep != 'all' also when tied on the first columns (:issue:`22752`) - :meth:`~pandas.io.formats.style.Styler.bar` now also supports tablewise application (in addition to rowwise and columnwise) with ``axis=None`` and setting clipping range with ``vmin`` and ``vmax`` (:issue:`21548` and :issue:`21526`). ``NaN`` values are also handled properly. - Logical operations ``&, |, ^`` between :class:`Series` and :class:`Index` will no longer raise ``ValueError`` (:issue:`22092`) -- diff --git a/pandas/core/computation/pytables.py b/pandas/core/computation/pytables.py index 2bd1b0c5b35079..e08df3e3401386 100644 --- a/pandas/core/computation/pytables.py +++ b/pandas/core/computation/pytables.py @@ -411,7 +411,7 @@ def visit_Subscript(self, node, **kwargs): slobj = self.visit(node.slice) try: value = value.value - except: + except AttributeError: pass try: diff --git a/pandas/core/dtypes/common.py b/pandas/core/dtypes/common.py index e2b9e246aee50f..5f0b71d4505c27 100644 --- a/pandas/core/dtypes/common.py +++ b/pandas/core/dtypes/common.py @@ -467,7 +467,7 @@ def is_timedelta64_dtype(arr_or_dtype): return False try: tipo = _get_dtype_type(arr_or_dtype) - except: + except (TypeError, ValueError, SyntaxError): return False return issubclass(tipo, np.timedelta64) diff --git a/pandas/core/dtypes/dtypes.py b/pandas/core/dtypes/dtypes.py index d879ded4f0f098..fe5cc9389a8baf 100644 --- a/pandas/core/dtypes/dtypes.py +++ b/pandas/core/dtypes/dtypes.py @@ -358,11 +358,11 @@ def construct_from_string(cls, string): try: if string == 'category': return cls() - except: + else: + raise TypeError("cannot construct a CategoricalDtype") + except AttributeError: pass - raise TypeError("cannot construct a CategoricalDtype") - @staticmethod def validate_ordered(ordered): """ @@ -519,7 +519,7 @@ def __new__(cls, unit=None, tz=None): if m is not None: unit = m.groupdict()['unit'] tz = m.groupdict()['tz'] - except: + except TypeError: raise ValueError("could not construct DatetimeTZDtype") elif isinstance(unit, compat.string_types): diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 15cebb88faea73..abe8a519afe1bc 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -3260,7 +3260,7 @@ def _ensure_valid_index(self, value): if not len(self.index) and is_list_like(value): try: value = Series(value) - except: + except (ValueError, NotImplementedError, TypeError): raise ValueError('Cannot set a frame with no defined index ' 'and a value that cannot be converted to a ' 'Series') @@ -7750,7 +7750,7 @@ def convert(v): values = np.array([convert(v) for v in values]) else: values = convert(values) - except: + except (ValueError, TypeError): values = convert(values) else: diff --git a/pandas/core/indexes/frozen.py b/pandas/core/indexes/frozen.py index 5a37e03b700f93..289970aaf3a828 100644 --- a/pandas/core/indexes/frozen.py +++ b/pandas/core/indexes/frozen.py @@ -139,7 +139,7 @@ def searchsorted(self, value, side="left", sorter=None): # xref: https://github.com/numpy/numpy/issues/5370 try: value = self.dtype.type(value) - except: + except ValueError: pass return super(FrozenNDArray, self).searchsorted( diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 3e6b934e1e8632..119a607fc0e685 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -6,6 +6,7 @@ import numpy as np from pandas._libs import algos as libalgos, index as libindex, lib, Timestamp +from pandas._libs import tslibs from pandas.compat import range, zip, lrange, lzip, map from pandas.compat.numpy import function as nv @@ -1002,12 +1003,13 @@ def _try_mi(k): return _try_mi(key) except (KeyError): raise - except: + except (IndexError, ValueError, TypeError): pass try: return _try_mi(Timestamp(key)) - except: + except (KeyError, TypeError, + IndexError, ValueError, tslibs.OutOfBoundsDatetime): pass raise InvalidIndexError(key) @@ -1686,7 +1688,7 @@ def append(self, other): # if all(isinstance(x, MultiIndex) for x in other): try: return MultiIndex.from_tuples(new_tuples, names=self.names) - except: + except (TypeError, IndexError): return Index(new_tuples) def argsort(self, *args, **kwargs): @@ -2315,7 +2317,7 @@ def maybe_droplevels(indexer, levels, drop_level): for i in sorted(levels, reverse=True): try: new_index = new_index.droplevel(i) - except: + except ValueError: # no dropping here return orig_index @@ -2818,7 +2820,7 @@ def _convert_can_do_setop(self, other): msg = 'other must be a MultiIndex or a list of tuples' try: other = MultiIndex.from_tuples(other) - except: + except TypeError: raise TypeError(msg) else: result_names = self.names if self.names == other.names else None diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index b63f874abff85e..150518aadcfd93 100755 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -2146,7 +2146,7 @@ def _getitem_tuple(self, tup): self._has_valid_tuple(tup) try: return self._getitem_lowerdim(tup) - except: + except IndexingError: pass retval = self.obj @@ -2705,13 +2705,13 @@ def maybe_droplevels(index, key): for _ in key: try: index = index.droplevel(0) - except: + except ValueError: # we have dropped too much, so back out return original_index else: try: index = index.droplevel(0) - except: + except ValueError: pass return index diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index 6576db9f642a68..0e57dd33b1c4ea 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -666,7 +666,7 @@ def _astype(self, dtype, copy=False, errors='raise', values=None, newb = make_block(values, placement=self.mgr_locs, klass=klass, ndim=self.ndim) - except: + except Exception: # noqa: E722 if errors == 'raise': raise newb = self.copy() if copy else self @@ -1142,7 +1142,7 @@ def check_int_bool(self, inplace): # a fill na type method try: m = missing.clean_fill_method(method) - except: + except ValueError: m = None if m is not None: @@ -1157,7 +1157,7 @@ def check_int_bool(self, inplace): # try an interp method try: m = missing.clean_interp_method(method, **kwargs) - except: + except ValueError: m = None if m is not None: @@ -2438,7 +2438,7 @@ def set(self, locs, values, check=False): try: if (self.values[locs] == values).all(): return - except: + except (IndexError, ValueError): pass try: self.values[locs] = values @@ -3172,7 +3172,7 @@ def _astype(self, dtype, copy=False, errors='raise', values=None, def __len__(self): try: return self.sp_index.length - except: + except AttributeError: return 0 def copy(self, deep=True, mgr=None): diff --git a/pandas/core/nanops.py b/pandas/core/nanops.py index 7619d47cbc8f9b..232d030da7f1ea 100644 --- a/pandas/core/nanops.py +++ b/pandas/core/nanops.py @@ -503,7 +503,8 @@ def reduction(values, axis=None, skipna=True): try: result = getattr(values, meth)(axis, dtype=dtype_max) result.fill(np.nan) - except: + except (AttributeError, TypeError, + ValueError, np.core._internal.AxisError): result = np.nan else: result = getattr(values, meth)(axis) @@ -815,7 +816,7 @@ def _ensure_numeric(x): elif is_object_dtype(x): try: x = x.astype(np.complex128) - except: + except (TypeError, ValueError): x = x.astype(np.float64) else: if not np.any(x.imag): diff --git a/pandas/core/ops.py b/pandas/core/ops.py index 70fe7de0a973e7..ad187b08e07426 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -1545,7 +1545,8 @@ def na_op(x, y): y = bool(y) try: result = libops.scalar_binop(x, y, op) - except: + except (TypeError, ValueError, AttributeError, + OverflowError, NotImplementedError): raise TypeError("cannot compare a dtyped [{dtype}] array " "with a scalar of type [{typ}]" .format(dtype=x.dtype, diff --git a/pandas/core/sparse/array.py b/pandas/core/sparse/array.py index eb07e5ef6c85f5..186a2490a5f2ec 100644 --- a/pandas/core/sparse/array.py +++ b/pandas/core/sparse/array.py @@ -306,7 +306,7 @@ def __setstate__(self, state): def __len__(self): try: return self.sp_index.length - except: + except AttributeError: return 0 def __unicode__(self): diff --git a/pandas/core/tools/datetimes.py b/pandas/core/tools/datetimes.py index 4a5290a90313d5..eb8d2b0b6c809b 100644 --- a/pandas/core/tools/datetimes.py +++ b/pandas/core/tools/datetimes.py @@ -244,7 +244,7 @@ def _convert_listlike_datetimes(arg, box, format, name=None, tz=None, if format == '%Y%m%d': try: result = _attempt_YYYYMMDD(arg, errors=errors) - except: + except (ValueError, TypeError, tslibs.OutOfBoundsDatetime): raise ValueError("cannot convert the input to " "'%Y%m%d' date format") @@ -334,7 +334,7 @@ def _adjust_to_origin(arg, origin, unit): raise ValueError("unit must be 'D' for origin='julian'") try: arg = arg - j0 - except: + except TypeError: raise ValueError("incompatible 'arg' type for given " "'origin'='julian'") @@ -731,21 +731,21 @@ def calc_with_mask(carg, mask): # try intlike / strings that are ints try: return calc(arg.astype(np.int64)) - except: + except ValueError: pass # a float with actual np.nan try: carg = arg.astype(np.float64) return calc_with_mask(carg, notna(carg)) - except: + except ValueError: pass # string with NaN-like try: mask = ~algorithms.isin(arg, list(tslib.nat_strings)) return calc_with_mask(arg, mask) - except: + except ValueError: pass return None diff --git a/pandas/core/window.py b/pandas/core/window.py index 5cdf62d5a55372..4281d66a640e33 100644 --- a/pandas/core/window.py +++ b/pandas/core/window.py @@ -2504,7 +2504,7 @@ def _offset(window, center): offset = (window - 1) / 2. if center else 0 try: return int(offset) - except: + except TypeError: return offset.astype(int) From 08ecba8dab4a35ad3cad89fe02c7240674938b97 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 2 Oct 2018 14:22:53 -0700 Subject: [PATCH 57/85] BUG: fix DataFrame+DataFrame op with timedelta64 dtype (#22696) --- doc/source/whatsnew/v0.24.0.txt | 2 +- pandas/core/frame.py | 2 +- pandas/core/ops.py | 42 +++++++++++++++++++++++++-- pandas/tests/frame/test_arithmetic.py | 15 ++++++++++ 4 files changed, 57 insertions(+), 4 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index f83185173c3e31..9b71ab656920d0 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -666,7 +666,7 @@ Timedelta - Bug in :class:`Index` with numeric dtype when multiplying or dividing an array with dtype ``timedelta64`` (:issue:`22390`) - Bug in :class:`TimedeltaIndex` incorrectly allowing indexing with ``Timestamp`` object (:issue:`20464`) - Fixed bug where subtracting :class:`Timedelta` from an object-dtyped array would raise ``TypeError`` (:issue:`21980`) -- +- Fixed bug in adding a :class:`DataFrame` with all-`timedelta64[ns]` dtypes to a :class:`DataFrame` with all-integer dtypes returning incorrect results instead of raising ``TypeError`` (:issue:`22696`) - Timezones diff --git a/pandas/core/frame.py b/pandas/core/frame.py index abe8a519afe1bc..138d1017aa43d9 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -4889,7 +4889,7 @@ def _arith_op(left, right): left, right = ops.fill_binop(left, right, fill_value) return func(left, right) - if this._is_mixed_type or other._is_mixed_type: + if ops.should_series_dispatch(this, other, func): # iterate over columns return ops.dispatch_to_series(this, other, _arith_op) else: diff --git a/pandas/core/ops.py b/pandas/core/ops.py index ad187b08e07426..8171840c96b6e5 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -900,6 +900,42 @@ def invalid_comparison(left, right, op): return res_values +# ----------------------------------------------------------------------------- +# Dispatch logic + +def should_series_dispatch(left, right, op): + """ + Identify cases where a DataFrame operation should dispatch to its + Series counterpart. + + Parameters + ---------- + left : DataFrame + right : DataFrame + op : binary operator + + Returns + ------- + override : bool + """ + if left._is_mixed_type or right._is_mixed_type: + return True + + if not len(left.columns) or not len(right.columns): + # ensure obj.dtypes[0] exists for each obj + return False + + ldtype = left.dtypes.iloc[0] + rdtype = right.dtypes.iloc[0] + + if ((is_timedelta64_dtype(ldtype) and is_integer_dtype(rdtype)) or + (is_timedelta64_dtype(rdtype) and is_integer_dtype(ldtype))): + # numpy integer dtypes as timedelta64 dtypes in this scenario + return True + + return False + + # ----------------------------------------------------------------------------- # Functions that add arithmetic methods to objects, given arithmetic factory # methods @@ -1803,8 +1839,10 @@ def f(self, other, axis=default_axis, level=None, fill_value=None): other = _align_method_FRAME(self, other, axis) - if isinstance(other, ABCDataFrame): # Another DataFrame - return self._combine_frame(other, na_op, fill_value, level) + if isinstance(other, ABCDataFrame): + # Another DataFrame + pass_op = op if should_series_dispatch(self, other, op) else na_op + return self._combine_frame(other, pass_op, fill_value, level) elif isinstance(other, ABCSeries): return _combine_series_frame(self, other, na_op, fill_value=fill_value, axis=axis, diff --git a/pandas/tests/frame/test_arithmetic.py b/pandas/tests/frame/test_arithmetic.py index 2b08897864db01..2eb11c3a2e2f79 100644 --- a/pandas/tests/frame/test_arithmetic.py +++ b/pandas/tests/frame/test_arithmetic.py @@ -266,3 +266,18 @@ def test_df_bool_mul_int(self): result = 1 * df kinds = result.dtypes.apply(lambda x: x.kind) assert (kinds == 'i').all() + + def test_td64_df_add_int_frame(self): + # GH#22696 Check that we don't dispatch to numpy implementation, + # which treats int64 as m8[ns] + tdi = pd.timedelta_range('1', periods=3) + df = tdi.to_frame() + other = pd.DataFrame([1, 2, 3], index=tdi) # indexed like `df` + with pytest.raises(TypeError): + df + other + with pytest.raises(TypeError): + other + df + with pytest.raises(TypeError): + df - other + with pytest.raises(TypeError): + other - df From b0f9a104f323d687a56ea878ff78ff005f37b42d Mon Sep 17 00:00:00 2001 From: Tony Tao <34781056+tonytao2012@users.noreply.github.com> Date: Tue, 2 Oct 2018 19:01:08 -0500 Subject: [PATCH 58/85] DOC GH22893 Fix docstring of groupby in pandas/core/generic.py (#22920) --- pandas/core/generic.py | 101 +++++++++++++++++++++++++++++------------ 1 file changed, 73 insertions(+), 28 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 8fed92f7ed6b96..cc157cc7228a84 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -7034,8 +7034,12 @@ def clip_lower(self, threshold, axis=None, inplace=False): def groupby(self, by=None, axis=0, level=None, as_index=True, sort=True, group_keys=True, squeeze=False, observed=False, **kwargs): """ - Group series using mapper (dict or key function, apply given function - to group, return result as series) or by a series of columns. + Group DataFrame or Series 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 ---------- @@ -7048,54 +7052,95 @@ def groupby(self, by=None, axis=0, level=None, as_index=True, sort=True, values are used as-is 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 a (single) key. - axis : int, default 0 + axis : {0 or 'index', 1 or 'columns'}, default 0 + Split along rows (0) or columns (1). level : int, level name, or sequence of such, default None If the axis is a MultiIndex (hierarchical), group by a particular - level or levels - as_index : boolean, default True + level or levels. + as_index : bool, default True For aggregated output, return object with group labels as the index. Only relevant for DataFrame input. as_index=False is - effectively "SQL-style" grouped output - sort : boolean, default True + effectively "SQL-style" grouped output. + 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. - group_keys : boolean, default True - When calling apply, add group keys to index to identify pieces - squeeze : boolean, default False - reduce the dimensionality of the return type if possible, - otherwise return a consistent type - observed : boolean, default False - This only applies if any of the groupers are Categoricals + group. Groupby preserves the order of rows within each group. + group_keys : bool, default True + When calling apply, add group keys to index to identify pieces. + squeeze : bool, default False + Reduce the dimensionality of the return type if possible, + otherwise return a consistent type. + 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. .. versionadded:: 0.23.0 + **kwargs + Optional, only accepts keyword argument 'mutated' and is passed + to groupby. + Returns ------- - GroupBy object + DataFrameGroupBy or SeriesGroupBy + Depends on the calling object and returns groupby object that + contains information about the groups. - Examples + See Also -------- - DataFrame results - - >>> data.groupby(func, axis=0).mean() - >>> data.groupby(['col1', 'col2'])['col3'].mean() - - DataFrame with hierarchical index - - >>> data.groupby(['col1', 'col2']).mean() + resample : Convenience method for frequency conversion and resampling + of time series. Notes ----- See the `user guide `_ for more. - See also + Examples -------- - resample : Convenience method for frequency conversion and resampling - of time series. + >>> df = pd.DataFrame({'Animal' : ['Falcon', 'Falcon', + ... 'Parrot', 'Parrot'], + ... 'Max Speed' : [380., 370., 24., 26.]}) + >>> df + Animal Max Speed + 0 Falcon 380.0 + 1 Falcon 370.0 + 2 Parrot 24.0 + 3 Parrot 26.0 + >>> df.groupby(['Animal']).mean() + Max Speed + Animal + Falcon 375.0 + Parrot 25.0 + + **Hierarchical Indexes** + + We can groupby different levels of a hierarchical index + using the `level` parameter: + + >>> arrays = [['Falcon', 'Falcon', 'Parrot', 'Parrot'], + ... ['Capitve', 'Wild', 'Capitve', 'Wild']] + >>> index = pd.MultiIndex.from_arrays(arrays, names=('Animal', 'Type')) + >>> df = pd.DataFrame({'Max Speed' : [390., 350., 30., 20.]}, + ... index=index) + >>> df + Max Speed + Animal Type + Falcon Capitve 390.0 + Wild 350.0 + Parrot Capitve 30.0 + Wild 20.0 + >>> df.groupby(level=0).mean() + Max Speed + Animal + Falcon 370.0 + Parrot 25.0 + >>> df.groupby(level=1).mean() + Max Speed + Type + Capitve 210.0 + Wild 185.0 """ from pandas.core.groupby.groupby import groupby From 04ea51ddf7623b897aaaf2e504952d3c11e88205 Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Wed, 3 Oct 2018 09:24:36 +0200 Subject: [PATCH 59/85] CLN: small clean-up of IntervalIndex (#22956) --- pandas/core/arrays/interval.py | 7 +---- pandas/core/indexes/interval.py | 49 ++++++--------------------------- 2 files changed, 9 insertions(+), 47 deletions(-) diff --git a/pandas/core/arrays/interval.py b/pandas/core/arrays/interval.py index 90df596b982966..134999f05364f8 100644 --- a/pandas/core/arrays/interval.py +++ b/pandas/core/arrays/interval.py @@ -108,12 +108,7 @@ class IntervalArray(IntervalMixin, ExtensionArray): _na_value = _fill_value = np.nan def __new__(cls, data, closed=None, dtype=None, copy=False, - fastpath=False, verify_integrity=True): - - if fastpath: - return cls._simple_new(data.left, data.right, closed, - copy=copy, dtype=dtype, - verify_integrity=False) + verify_integrity=True): if isinstance(data, ABCSeries) and is_interval_dtype(data): data = data.values diff --git a/pandas/core/indexes/interval.py b/pandas/core/indexes/interval.py index 4b125580bd7e0b..f72f87aeb2af6e 100644 --- a/pandas/core/indexes/interval.py +++ b/pandas/core/indexes/interval.py @@ -146,17 +146,13 @@ class IntervalIndex(IntervalMixin, Index): _mask = None def __new__(cls, data, closed=None, dtype=None, copy=False, - name=None, fastpath=False, verify_integrity=True): - - if fastpath: - return cls._simple_new(data, name) + name=None, verify_integrity=True): if name is None and hasattr(data, 'name'): name = data.name with rewrite_exception("IntervalArray", cls.__name__): array = IntervalArray(data, closed=closed, copy=copy, dtype=dtype, - fastpath=fastpath, verify_integrity=verify_integrity) return cls._simple_new(array, name) @@ -187,14 +183,6 @@ def _shallow_copy(self, left=None, right=None, **kwargs): attributes.update(kwargs) return self._simple_new(result, **attributes) - @cache_readonly - def hasnans(self): - """ - Return if the IntervalIndex has any nans; enables various performance - speedups - """ - return self._isnan.any() - @cache_readonly def _isnan(self): """Return a mask indicating if each value is NA""" @@ -206,10 +194,6 @@ def _isnan(self): def _engine(self): return IntervalTree(self.left, self.right, closed=self.closed) - @property - def _constructor(self): - return type(self) - def __contains__(self, key): """ return a boolean if this key is IN the index @@ -394,18 +378,7 @@ def _values(self): @cache_readonly def _ndarray_values(self): - left = self.left - right = self.right - mask = self._isnan - closed = self.closed - - result = np.empty(len(left), dtype=object) - for i in range(len(left)): - if mask[i]: - result[i] = np.nan - else: - result[i] = Interval(left[i], right[i], closed) - return result + return np.array(self._data) def __array__(self, result=None): """ the array interface, return my values """ @@ -892,18 +865,12 @@ def take(self, indices, axis=0, allow_fill=True, return self._simple_new(result, **attributes) def __getitem__(self, value): - mask = self._isnan[value] - if is_scalar(mask) and mask: - return self._na_value - - left = self.left[value] - right = self.right[value] - - # scalar - if not isinstance(left, Index): - return Interval(left, right, self.closed) - - return self._shallow_copy(left, right) + result = self._data[value] + if isinstance(result, IntervalArray): + return self._shallow_copy(result) + else: + # scalar + return result # __repr__ associated methods are based on MultiIndex From 03181f0569c8b1f93f620a2986b4f174f9b6179b Mon Sep 17 00:00:00 2001 From: Wenhuan Date: Wed, 3 Oct 2018 15:28:07 +0800 Subject: [PATCH 60/85] BUG: fix Series(extension array) + extension array values addition (#22479) --- pandas/core/ops.py | 2 +- pandas/tests/extension/base/ops.py | 6 ++++++ pandas/tests/extension/json/test_json.py | 5 +++++ pandas/tests/extension/test_categorical.py | 6 ++++++ pandas/tests/extension/test_integer.py | 6 ++++++ 5 files changed, 24 insertions(+), 1 deletion(-) diff --git a/pandas/core/ops.py b/pandas/core/ops.py index 8171840c96b6e5..a02152a123b48e 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -1218,7 +1218,7 @@ def dispatch_to_extension_op(op, left, right): new_right = [new_right] new_right = list(new_right) elif is_extension_array_dtype(right) and type(left) != type(right): - new_right = list(new_right) + new_right = list(right) else: new_right = right diff --git a/pandas/tests/extension/base/ops.py b/pandas/tests/extension/base/ops.py index 05351c56862b80..ee4a92146128ba 100644 --- a/pandas/tests/extension/base/ops.py +++ b/pandas/tests/extension/base/ops.py @@ -77,6 +77,12 @@ def test_divmod(self, data): self._check_divmod_op(s, divmod, 1, exc=TypeError) self._check_divmod_op(1, ops.rdivmod, s, exc=TypeError) + def test_add_series_with_extension_array(self, data): + s = pd.Series(data) + result = s + data + expected = pd.Series(data + data) + self.assert_series_equal(result, expected) + def test_error(self, data, all_arithmetic_operators): # invalid ops op_name = all_arithmetic_operators diff --git a/pandas/tests/extension/json/test_json.py b/pandas/tests/extension/json/test_json.py index 0126d771caf7fa..93f10b7fbfc232 100644 --- a/pandas/tests/extension/json/test_json.py +++ b/pandas/tests/extension/json/test_json.py @@ -261,6 +261,11 @@ class TestArithmeticOps(BaseJSON, base.BaseArithmeticOpsTests): def test_error(self, data, all_arithmetic_operators): pass + def test_add_series_with_extension_array(self, data): + ser = pd.Series(data) + with tm.assert_raises_regex(TypeError, "unsupported"): + ser + data + class TestComparisonOps(BaseJSON, base.BaseComparisonOpsTests): pass diff --git a/pandas/tests/extension/test_categorical.py b/pandas/tests/extension/test_categorical.py index ff66f53eab6f6c..c588552572aedd 100644 --- a/pandas/tests/extension/test_categorical.py +++ b/pandas/tests/extension/test_categorical.py @@ -22,6 +22,7 @@ from pandas.api.types import CategoricalDtype from pandas import Categorical from pandas.tests.extension import base +import pandas.util.testing as tm def make_data(): @@ -202,6 +203,11 @@ def test_arith_series_with_scalar(self, data, all_arithmetic_operators): else: pytest.skip('rmod never called when string is first argument') + def test_add_series_with_extension_array(self, data): + ser = pd.Series(data) + with tm.assert_raises_regex(TypeError, "cannot perform"): + ser + data + class TestComparisonOps(base.BaseComparisonOpsTests): diff --git a/pandas/tests/extension/test_integer.py b/pandas/tests/extension/test_integer.py index 7aa33006daddab..fa5c89d85e5481 100644 --- a/pandas/tests/extension/test_integer.py +++ b/pandas/tests/extension/test_integer.py @@ -143,6 +143,12 @@ def test_error(self, data, all_arithmetic_operators): # other specific errors tested in the integer array specific tests pass + @pytest.mark.xfail(reason="EA is listified. GH-22922", strict=True) + def test_add_series_with_extension_array(self, data): + super(TestArithmeticOps, self).test_add_series_with_extension_array( + data + ) + class TestComparisonOps(base.BaseComparisonOpsTests): From e756e991d57c2656906d0a3e8fc76950844e3f3e Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Wed, 3 Oct 2018 02:19:27 -0700 Subject: [PATCH 61/85] CLN: Use is_period_dtype instead of ABCPeriodIndex checks (#22958) --- pandas/core/arrays/period.py | 2 +- pandas/core/indexes/datetimelike.py | 18 +++++++++--------- pandas/core/indexes/multi.py | 2 +- pandas/core/indexes/range.py | 18 +++++++++--------- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 481d5313f0e25c..41b4c5c669efcc 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -264,7 +264,7 @@ def asfreq(self, freq=None, how='E'): if self.hasnans: new_data[self._isnan] = iNaT - return self._simple_new(new_data, self.name, freq=freq) + return self._shallow_copy(new_data, freq=freq) # ------------------------------------------------------------------ # Arithmetic Methods diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 37a12a588db035..1ec30ecbb3a3be 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -21,6 +21,7 @@ is_list_like, is_scalar, is_bool_dtype, + is_period_dtype, is_categorical_dtype, is_datetime_or_timedelta_dtype, is_float_dtype, @@ -28,7 +29,7 @@ is_object_dtype, is_string_dtype) from pandas.core.dtypes.generic import ( - ABCIndex, ABCSeries, ABCPeriodIndex, ABCIndexClass) + ABCIndex, ABCSeries, ABCIndexClass) from pandas.core.dtypes.missing import isna from pandas.core import common as com, algorithms, ops @@ -239,9 +240,8 @@ def equals(self, other): # have different timezone return False - # ToDo: Remove this when PeriodDtype is added - elif isinstance(self, ABCPeriodIndex): - if not isinstance(other, ABCPeriodIndex): + elif is_period_dtype(self): + if not is_period_dtype(other): return False if self.freq != other.freq: return False @@ -359,7 +359,7 @@ def sort_values(self, return_indexer=False, ascending=True): attribs = self._get_attributes_dict() freq = attribs['freq'] - if freq is not None and not isinstance(self, ABCPeriodIndex): + if freq is not None and not is_period_dtype(self): if freq.n > 0 and not ascending: freq = freq * -1 elif freq.n < 0 and ascending: @@ -386,8 +386,8 @@ def take(self, indices, axis=0, allow_fill=True, fill_value=fill_value, na_value=iNaT) - # keep freq in PeriodIndex, reset otherwise - freq = self.freq if isinstance(self, ABCPeriodIndex) else None + # keep freq in PeriodArray/Index, reset otherwise + freq = self.freq if is_period_dtype(self) else None return self._shallow_copy(taken, freq=freq) _can_hold_na = True @@ -618,7 +618,7 @@ def repeat(self, repeats, *args, **kwargs): Analogous to ndarray.repeat """ nv.validate_repeat(args, kwargs) - if isinstance(self, ABCPeriodIndex): + if is_period_dtype(self): freq = self.freq else: freq = None @@ -673,7 +673,7 @@ def _concat_same_dtype(self, to_concat, name): attribs = self._get_attributes_dict() attribs['name'] = name - if not isinstance(self, ABCPeriodIndex): + if not is_period_dtype(self): # reset freq attribs['freq'] = None diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 119a607fc0e685..6091df776a01b2 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -1001,7 +1001,7 @@ def _try_mi(k): (compat.PY3 and isinstance(key, compat.string_types))): try: return _try_mi(key) - except (KeyError): + except KeyError: raise except (IndexError, ValueError, TypeError): pass diff --git a/pandas/core/indexes/range.py b/pandas/core/indexes/range.py index 981bfddeadac16..fd8e17c369f5a6 100644 --- a/pandas/core/indexes/range.py +++ b/pandas/core/indexes/range.py @@ -512,33 +512,33 @@ def __getitem__(self, key): # This is basically PySlice_GetIndicesEx, but delegation to our # super routines if we don't have integers - l = len(self) + length = len(self) # complete missing slice information step = 1 if key.step is None else key.step if key.start is None: - start = l - 1 if step < 0 else 0 + start = length - 1 if step < 0 else 0 else: start = key.start if start < 0: - start += l + start += length if start < 0: start = -1 if step < 0 else 0 - if start >= l: - start = l - 1 if step < 0 else l + if start >= length: + start = length - 1 if step < 0 else length if key.stop is None: - stop = -1 if step < 0 else l + stop = -1 if step < 0 else length else: stop = key.stop if stop < 0: - stop += l + stop += length if stop < 0: stop = -1 - if stop > l: - stop = l + if stop > length: + stop = length # delegate non-integer slices if (start != int(start) or From 3e3256bb6038111812b4b28f6b3b049214d83d2d Mon Sep 17 00:00:00 2001 From: alimcmaster1 Date: Wed, 3 Oct 2018 12:23:22 +0100 Subject: [PATCH 62/85] Allow passing a mask to NanOps (#22865) --- pandas/core/nanops.py | 404 ++++++++++++++++++++++++++++++++---- pandas/tests/test_nanops.py | 36 +++- 2 files changed, 391 insertions(+), 49 deletions(-) diff --git a/pandas/core/nanops.py b/pandas/core/nanops.py index 232d030da7f1ea..2884bc1a194917 100644 --- a/pandas/core/nanops.py +++ b/pandas/core/nanops.py @@ -1,12 +1,16 @@ -import itertools import functools +import itertools import operator import warnings from distutils.version import LooseVersion import numpy as np + +import pandas.core.common as com from pandas import compat from pandas._libs import tslibs, lib +from pandas.core.config import get_option +from pandas.core.dtypes.cast import _int64_max, maybe_upcast_putmask from pandas.core.dtypes.common import ( _get_dtype, is_float, is_scalar, @@ -17,10 +21,7 @@ is_datetime64_dtype, is_timedelta64_dtype, is_datetime_or_timedelta_dtype, is_int_or_datetime_dtype, is_any_int_dtype) -from pandas.core.dtypes.cast import _int64_max, maybe_upcast_putmask from pandas.core.dtypes.missing import isna, notna, na_value_for_dtype -from pandas.core.config import get_option -import pandas.core.common as com _BOTTLENECK_INSTALLED = False _MIN_BOTTLENECK_VERSION = '1.0.0' @@ -200,16 +201,18 @@ def _get_fill_value(dtype, fill_value=None, fill_value_typ=None): def _get_values(values, skipna, fill_value=None, fill_value_typ=None, - isfinite=False, copy=True): + isfinite=False, copy=True, mask=None): """ utility to get the values view, mask, dtype if necessary copy and mask using the specified fill_value copy = True will force the copy """ values = com.values_from_object(values) - if isfinite: - mask = _isfinite(values) - else: - mask = isna(values) + + if mask is None: + if isfinite: + mask = _isfinite(values) + else: + mask = isna(values) dtype = values.dtype dtype_ok = _na_ok_dtype(dtype) @@ -315,19 +318,98 @@ def _na_for_min_count(values, axis): return result -def nanany(values, axis=None, skipna=True): - values, mask, dtype, _ = _get_values(values, skipna, False, copy=skipna) +def nanany(values, axis=None, skipna=True, mask=None): + """ + Check if any elements along an axis evaluate to True. + + Parameters + ---------- + values : ndarray + axis : int, optional + skipna : bool, default True + mask : ndarray[bool], optional + nan-mask if known + + Returns + ------- + result : bool + + Examples + -------- + >>> import pandas.core.nanops as nanops + >>> s = pd.Series([1, 2]) + >>> nanops.nanany(s) + True + + >>> import pandas.core.nanops as nanops + >>> s = pd.Series([np.nan]) + >>> nanops.nanany(s) + False + """ + values, mask, dtype, _ = _get_values(values, skipna, False, copy=skipna, + mask=mask) return values.any(axis) -def nanall(values, axis=None, skipna=True): - values, mask, dtype, _ = _get_values(values, skipna, True, copy=skipna) +def nanall(values, axis=None, skipna=True, mask=None): + """ + Check if all elements along an axis evaluate to True. + + Parameters + ---------- + values : ndarray + axis: int, optional + skipna : bool, default True + mask : ndarray[bool], optional + nan-mask if known + + Returns + ------- + result : bool + + Examples + -------- + >>> import pandas.core.nanops as nanops + >>> s = pd.Series([1, 2, np.nan]) + >>> nanops.nanall(s) + True + + >>> import pandas.core.nanops as nanops + >>> s = pd.Series([1, 0]) + >>> nanops.nanall(s) + False + """ + values, mask, dtype, _ = _get_values(values, skipna, True, copy=skipna, + mask=mask) return values.all(axis) @disallow('M8') -def nansum(values, axis=None, skipna=True, min_count=0): - values, mask, dtype, dtype_max = _get_values(values, skipna, 0) +def nansum(values, axis=None, skipna=True, min_count=0, mask=None): + """ + Sum the elements along an axis ignoring NaNs + + Parameters + ---------- + values : ndarray[dtype] + axis: int, optional + skipna : bool, default True + min_count: int, default 0 + mask : ndarray[bool], optional + nan-mask if known + + Returns + ------- + result : dtype + + Examples + -------- + >>> import pandas.core.nanops as nanops + >>> s = pd.Series([1, 2, np.nan]) + >>> nanops.nansum(s) + 3.0 + """ + values, mask, dtype, dtype_max = _get_values(values, skipna, 0, mask=mask) dtype_sum = dtype_max if is_float_dtype(dtype): dtype_sum = dtype @@ -341,9 +423,32 @@ def nansum(values, axis=None, skipna=True, min_count=0): @disallow('M8') @bottleneck_switch() -def nanmean(values, axis=None, skipna=True): - values, mask, dtype, dtype_max = _get_values(values, skipna, 0) +def nanmean(values, axis=None, skipna=True, mask=None): + """ + Compute the mean of the element along an axis ignoring NaNs + Parameters + ---------- + values : ndarray + axis: int, optional + skipna : bool, default True + mask : ndarray[bool], optional + nan-mask if known + + Returns + ------- + result : float + Unless input is a float array, in which case use the same + precision as the input array. + + Examples + -------- + >>> import pandas.core.nanops as nanops + >>> s = pd.Series([1, 2, np.nan]) + >>> nanops.nanmean(s) + 1.5 + """ + values, mask, dtype, dtype_max = _get_values(values, skipna, 0, mask=mask) dtype_sum = dtype_max dtype_count = np.float64 if is_integer_dtype(dtype) or is_timedelta64_dtype(dtype): @@ -367,15 +472,36 @@ def nanmean(values, axis=None, skipna=True): @disallow('M8') @bottleneck_switch() -def nanmedian(values, axis=None, skipna=True): +def nanmedian(values, axis=None, skipna=True, mask=None): + """ + Parameters + ---------- + values : ndarray + axis: int, optional + skipna : bool, default True + mask : ndarray[bool], optional + nan-mask if known + Returns + ------- + result : float + Unless input is a float array, in which case use the same + precision as the input array. + + Examples + -------- + >>> import pandas.core.nanops as nanops + >>> s = pd.Series([1, np.nan, 2, 2]) + >>> nanops.nanmedian(s) + 2.0 + """ def get_median(x): mask = notna(x) if not skipna and not mask.all(): return np.nan return np.nanmedian(x[mask]) - values, mask, dtype, dtype_max = _get_values(values, skipna) + values, mask, dtype, dtype_max = _get_values(values, skipna, mask=mask) if not is_float_dtype(values): values = values.astype('f8') values[mask] = np.nan @@ -431,18 +557,73 @@ def _get_counts_nanvar(mask, axis, ddof, dtype=float): @disallow('M8') @bottleneck_switch(ddof=1) -def nanstd(values, axis=None, skipna=True, ddof=1): - result = np.sqrt(nanvar(values, axis=axis, skipna=skipna, ddof=ddof)) +def nanstd(values, axis=None, skipna=True, ddof=1, mask=None): + """ + Compute the standard deviation along given axis while ignoring NaNs + + Parameters + ---------- + values : ndarray + axis: int, optional + skipna : bool, default True + ddof : int, default 1 + Delta Degrees of Freedom. The divisor used in calculations is N - ddof, + where N represents the number of elements. + mask : ndarray[bool], optional + nan-mask if known + + Returns + ------- + result : float + Unless input is a float array, in which case use the same + precision as the input array. + + Examples + -------- + >>> import pandas.core.nanops as nanops + >>> s = pd.Series([1, np.nan, 2, 3]) + >>> nanops.nanstd(s) + 1.0 + """ + result = np.sqrt(nanvar(values, axis=axis, skipna=skipna, ddof=ddof, + mask=mask)) return _wrap_results(result, values.dtype) @disallow('M8') @bottleneck_switch(ddof=1) -def nanvar(values, axis=None, skipna=True, ddof=1): +def nanvar(values, axis=None, skipna=True, ddof=1, mask=None): + """ + Compute the variance along given axis while ignoring NaNs + + Parameters + ---------- + values : ndarray + axis: int, optional + skipna : bool, default True + ddof : int, default 1 + Delta Degrees of Freedom. The divisor used in calculations is N - ddof, + where N represents the number of elements. + mask : ndarray[bool], optional + nan-mask if known + Returns + ------- + result : float + Unless input is a float array, in which case use the same + precision as the input array. + + Examples + -------- + >>> import pandas.core.nanops as nanops + >>> s = pd.Series([1, np.nan, 2, 3]) + >>> nanops.nanvar(s) + 1.0 + """ values = com.values_from_object(values) dtype = values.dtype - mask = isna(values) + if mask is None: + mask = isna(values) if is_any_int_dtype(values): values = values.astype('f8') values[mask] = np.nan @@ -465,7 +646,7 @@ def nanvar(values, axis=None, skipna=True, ddof=1): avg = _ensure_numeric(values.sum(axis=axis, dtype=np.float64)) / count if axis is not None: avg = np.expand_dims(avg, axis) - sqr = _ensure_numeric((avg - values)**2) + sqr = _ensure_numeric((avg - values) ** 2) np.putmask(sqr, mask, 0) result = sqr.sum(axis=axis, dtype=np.float64) / d @@ -478,12 +659,41 @@ def nanvar(values, axis=None, skipna=True, ddof=1): @disallow('M8', 'm8') -def nansem(values, axis=None, skipna=True, ddof=1): +def nansem(values, axis=None, skipna=True, ddof=1, mask=None): + """ + Compute the standard error in the mean along given axis while ignoring NaNs + + Parameters + ---------- + values : ndarray + axis: int, optional + skipna : bool, default True + ddof : int, default 1 + Delta Degrees of Freedom. The divisor used in calculations is N - ddof, + where N represents the number of elements. + mask : ndarray[bool], optional + nan-mask if known + + Returns + ------- + result : float64 + Unless input is a float array, in which case use the same + precision as the input array. + + Examples + -------- + >>> import pandas.core.nanops as nanops + >>> s = pd.Series([1, np.nan, 2, 3]) + >>> nanops.nansem(s) + 0.5773502691896258 + """ + # This checks if non-numeric-like data is passed with numeric_only=False # and raises a TypeError otherwise - nanvar(values, axis, skipna, ddof=ddof) + nanvar(values, axis, skipna, ddof=ddof, mask=mask) - mask = isna(values) + if mask is None: + mask = isna(values) if not is_float_dtype(values.dtype): values = values.astype('f8') count, _ = _get_counts_nanvar(mask, axis, ddof, values.dtype) @@ -494,9 +704,9 @@ def nansem(values, axis=None, skipna=True, ddof=1): def _nanminmax(meth, fill_value_typ): @bottleneck_switch() - def reduction(values, axis=None, skipna=True): + def reduction(values, axis=None, skipna=True, mask=None): values, mask, dtype, dtype_max = _get_values( - values, skipna, fill_value_typ=fill_value_typ, ) + values, skipna, fill_value_typ=fill_value_typ, mask=mask) if ((axis is not None and values.shape[axis] == 0) or values.size == 0): @@ -521,39 +731,97 @@ def reduction(values, axis=None, skipna=True): @disallow('O') -def nanargmax(values, axis=None, skipna=True): +def nanargmax(values, axis=None, skipna=True, mask=None): """ - Returns -1 in the NA case + Parameters + ---------- + values : ndarray + axis: int, optional + skipna : bool, default True + mask : ndarray[bool], optional + nan-mask if known + + Returns + -------- + result : int + The index of max value in specified axis or -1 in the NA case + + Examples + -------- + >>> import pandas.core.nanops as nanops + >>> s = pd.Series([1, 2, 3, np.nan, 4]) + >>> nanops.nanargmax(s) + 4 """ - values, mask, dtype, _ = _get_values(values, skipna, fill_value_typ='-inf') + values, mask, dtype, _ = _get_values(values, skipna, fill_value_typ='-inf', + mask=mask) result = values.argmax(axis) result = _maybe_arg_null_out(result, axis, mask, skipna) return result @disallow('O') -def nanargmin(values, axis=None, skipna=True): +def nanargmin(values, axis=None, skipna=True, mask=None): """ - Returns -1 in the NA case + Parameters + ---------- + values : ndarray + axis: int, optional + skipna : bool, default True + mask : ndarray[bool], optional + nan-mask if known + + Returns + -------- + result : int + The index of min value in specified axis or -1 in the NA case + + Examples + -------- + >>> import pandas.core.nanops as nanops + >>> s = pd.Series([1, 2, 3, np.nan, 4]) + >>> nanops.nanargmin(s) + 0 """ - values, mask, dtype, _ = _get_values(values, skipna, fill_value_typ='+inf') + values, mask, dtype, _ = _get_values(values, skipna, fill_value_typ='+inf', + mask=mask) result = values.argmin(axis) result = _maybe_arg_null_out(result, axis, mask, skipna) return result @disallow('M8', 'm8') -def nanskew(values, axis=None, skipna=True): +def nanskew(values, axis=None, skipna=True, mask=None): """ Compute the sample skewness. The statistic computed here is the adjusted Fisher-Pearson standardized moment coefficient G1. The algorithm computes this coefficient directly from the second and third central moment. - """ + Parameters + ---------- + values : ndarray + axis: int, optional + skipna : bool, default True + mask : ndarray[bool], optional + nan-mask if known + Returns + ------- + result : float64 + Unless input is a float array, in which case use the same + precision as the input array. + + Examples + -------- + >>> import pandas.core.nanops as nanops + >>> s = pd.Series([1,np.nan, 1, 2]) + >>> nanops.nanskew(s) + 1.7320508075688787 + """ values = com.values_from_object(values) - mask = isna(values) + if mask is None: + mask = isna(values) if not is_float_dtype(values.dtype): values = values.astype('f8') count = _get_counts(mask, axis) @@ -602,16 +870,38 @@ def nanskew(values, axis=None, skipna=True): @disallow('M8', 'm8') -def nankurt(values, axis=None, skipna=True): - """ Compute the sample excess kurtosis. +def nankurt(values, axis=None, skipna=True, mask=None): + """ + Compute the sample excess kurtosis The statistic computed here is the adjusted Fisher-Pearson standardized moment coefficient G2, computed directly from the second and fourth central moment. + Parameters + ---------- + values : ndarray + axis: int, optional + skipna : bool, default True + mask : ndarray[bool], optional + nan-mask if known + + Returns + ------- + result : float64 + Unless input is a float array, in which case use the same + precision as the input array. + + Examples + -------- + >>> import pandas.core.nanops as nanops + >>> s = pd.Series([1,np.nan, 1, 3, 2]) + >>> nanops.nankurt(s) + -1.2892561983471076 """ values = com.values_from_object(values) - mask = isna(values) + if mask is None: + mask = isna(values) if not is_float_dtype(values.dtype): values = values.astype('f8') count = _get_counts(mask, axis) @@ -637,7 +927,7 @@ def nankurt(values, axis=None, skipna=True): with np.errstate(invalid='ignore', divide='ignore'): adj = 3 * (count - 1) ** 2 / ((count - 2) * (count - 3)) numer = count * (count + 1) * (count - 1) * m4 - denom = (count - 2) * (count - 3) * m2**2 + denom = (count - 2) * (count - 3) * m2 ** 2 # floating point error # @@ -669,8 +959,34 @@ def nankurt(values, axis=None, skipna=True): @disallow('M8', 'm8') -def nanprod(values, axis=None, skipna=True, min_count=0): - mask = isna(values) +def nanprod(values, axis=None, skipna=True, min_count=0, mask=None): + """ + Parameters + ---------- + values : ndarray[dtype] + axis: int, optional + skipna : bool, default True + min_count: int, default 0 + mask : ndarray[bool], optional + nan-mask if known + + Returns + ------- + result : dtype + + Examples + -------- + >>> import pandas.core.nanops as nanops + >>> s = pd.Series([1, 2, 3, np.nan]) + >>> nanops.nanprod(s) + 6.0 + + Returns + -------- + The product of all elements on a given axis. ( NaNs are treated as 1) + """ + if mask is None: + mask = isna(values) if skipna and not is_any_int_dtype(values): values = values.copy() values[mask] = 1 diff --git a/pandas/tests/test_nanops.py b/pandas/tests/test_nanops.py index b6c2c65fb6dce1..b06463d3c07aa9 100644 --- a/pandas/tests/test_nanops.py +++ b/pandas/tests/test_nanops.py @@ -1,19 +1,19 @@ # -*- coding: utf-8 -*- from __future__ import division, print_function +import warnings from functools import partial -import pytest -import warnings import numpy as np +import pytest import pandas as pd -from pandas import Series, isna -from pandas.core.dtypes.common import is_integer_dtype import pandas.core.nanops as nanops -import pandas.util.testing as tm import pandas.util._test_decorators as td +import pandas.util.testing as tm +from pandas import Series, isna from pandas.compat.numpy import _np_version_under1p13 +from pandas.core.dtypes.common import is_integer_dtype use_bn = nanops._USE_BOTTLENECK @@ -1041,3 +1041,29 @@ def test_numpy_ops_np_version_under1p13(numpy_op, expected): assert result == expected else: assert result == expected + + +@pytest.mark.parametrize("operation", [ + nanops.nanany, + nanops.nanall, + nanops.nansum, + nanops.nanmean, + nanops.nanmedian, + nanops.nanstd, + nanops.nanvar, + nanops.nansem, + nanops.nanargmax, + nanops.nanargmin, + nanops.nanmax, + nanops.nanmin, + nanops.nanskew, + nanops.nankurt, + nanops.nanprod, +]) +def test_nanops_independent_of_mask_param(operation): + # GH22764 + s = pd.Series([1, 2, np.nan, 3, np.nan, 4]) + mask = s.isna() + median_expected = operation(s) + median_result = operation(s, mask=mask) + assert median_expected == median_result From 15d32bbad832908c9d06a9019e613bb6b35d6878 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Wed, 3 Oct 2018 04:32:35 -0700 Subject: [PATCH 63/85] [CLN] Dispatch (some) Frame ops to Series, avoiding _data.eval (#22019) * avoid casting to object dtype in mixed-type frames * Dispatch to Series ops in _combine_match_columns * comment * docstring * flake8 fixup * dont bother with try_cast_result * revert non-central change * simplify * revert try_cast_results * revert non-central changes * Fixup typo syntaxerror * simplify assertion * use dispatch_to_series in combine_match_columns * Pass unwrapped op where appropriate * catch correct error * whatsnew note * comment * whatsnew section * remove unnecessary tester * doc fixup --- doc/source/whatsnew/v0.24.0.txt | 29 ++++++++++++++++ pandas/core/frame.py | 7 +--- pandas/core/ops.py | 17 ++++++++-- pandas/tests/arithmetic/test_timedelta64.py | 34 ++++++++----------- .../tests/frame/test_axis_select_reindex.py | 2 +- pandas/tests/reshape/test_pivot.py | 8 +++-- pandas/tests/series/test_operators.py | 10 +++--- 7 files changed, 70 insertions(+), 37 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 9b71ab656920d0..700916ba6066ea 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -532,6 +532,35 @@ Current Behavior: ... OverflowError: Trying to coerce negative values to unsigned integers +.. _whatsnew_0240.api.crosstab_dtypes + +Crosstab Preserves Dtypes +^^^^^^^^^^^^^^^^^^^^^^^^^ + +:func:`crosstab` will preserve now dtypes in some cases that previously would +cast from integer dtype to floating dtype (:issue:`22019`) + +Previous Behavior: + +.. code-block:: ipython + + In [3]: df = pd.DataFrame({'a': [1, 2, 2, 2, 2], 'b': [3, 3, 4, 4, 4], + ...: 'c': [1, 1, np.nan, 1, 1]}) + In [4]: pd.crosstab(df.a, df.b, normalize='columns') + Out[4]: + b 3 4 + a + 1 0.5 0.0 + 2 0.5 1.0 + +Current Behavior: + +.. code-block:: ipython + + In [3]: df = pd.DataFrame({'a': [1, 2, 2, 2, 2], 'b': [3, 3, 4, 4, 4], + ...: 'c': [1, 1, np.nan, 1, 1]}) + In [4]: pd.crosstab(df.a, df.b, normalize='columns') + Datetimelike API Changes ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 138d1017aa43d9..ff7590f6d53582 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -4899,7 +4899,6 @@ def _arith_op(left, right): copy=False) def _combine_match_index(self, other, func, level=None): - assert isinstance(other, Series) left, right = self.align(other, join='outer', axis=0, level=level, copy=False) assert left.index.equals(right.index) @@ -4919,11 +4918,7 @@ def _combine_match_columns(self, other, func, level=None, try_cast=True): left, right = self.align(other, join='outer', axis=1, level=level, copy=False) assert left.columns.equals(right.index) - - new_data = left._data.eval(func=func, other=right, - axes=[left.columns, self.index], - try_cast=try_cast) - return self._constructor(new_data) + return ops.dispatch_to_series(left, right, func, axis="columns") def _combine_const(self, other, func, errors='raise', try_cast=True): if lib.is_scalar(other) or np.ndim(other) == 0: diff --git a/pandas/core/ops.py b/pandas/core/ops.py index a02152a123b48e..dc99faaf68f518 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -1666,7 +1666,7 @@ def flex_wrapper(self, other, level=None, fill_value=None, axis=0): # ----------------------------------------------------------------------------- # DataFrame -def dispatch_to_series(left, right, func, str_rep=None): +def dispatch_to_series(left, right, func, str_rep=None, axis=None): """ Evaluate the frame operation func(left, right) by evaluating column-by-column, dispatching to the Series implementation. @@ -1677,6 +1677,7 @@ def dispatch_to_series(left, right, func, str_rep=None): right : scalar or DataFrame func : arithmetic or comparison operator str_rep : str or None, default None + axis : {None, 0, 1, "index", "columns"} Returns ------- @@ -1700,6 +1701,15 @@ def column_op(a, b): return {i: func(a.iloc[:, i], b.iloc[:, i]) for i in range(len(a.columns))} + elif isinstance(right, ABCSeries) and axis == "columns": + # We only get here if called via left._combine_match_columns, + # in which case we specifically want to operate row-by-row + assert right.index.equals(left.columns) + + def column_op(a, b): + return {i: func(a.iloc[:, i], b.iloc[i]) + for i in range(len(a.columns))} + elif isinstance(right, ABCSeries): assert right.index.equals(left.index) # Handle other cases later @@ -1844,7 +1854,10 @@ def f(self, other, axis=default_axis, level=None, fill_value=None): pass_op = op if should_series_dispatch(self, other, op) else na_op return self._combine_frame(other, pass_op, fill_value, level) elif isinstance(other, ABCSeries): - return _combine_series_frame(self, other, na_op, + # For these values of `axis`, we end up dispatching to Series op, + # so do not want the masked op. + pass_op = op if axis in [0, "columns", None] else na_op + return _combine_series_frame(self, other, pass_op, fill_value=fill_value, axis=axis, level=level, try_cast=True) else: diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index 50509221735642..a09efe6d4761c7 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -505,33 +505,25 @@ def test_tdi_add_dt64_array(self, box_df_broadcast_failure): # ------------------------------------------------------------------ # Operations with int-like others - def test_td64arr_add_int_series_invalid(self, box_df_broadcast_failure, - tdser): - box = box_df_broadcast_failure + def test_td64arr_add_int_series_invalid(self, box, tdser): tdser = tm.box_expected(tdser, box) err = TypeError if box is not pd.Index else NullFrequencyError with pytest.raises(err): tdser + Series([2, 3, 4]) - def test_td64arr_radd_int_series_invalid(self, box_df_broadcast_failure, - tdser): - box = box_df_broadcast_failure + def test_td64arr_radd_int_series_invalid(self, box, tdser): tdser = tm.box_expected(tdser, box) err = TypeError if box is not pd.Index else NullFrequencyError with pytest.raises(err): Series([2, 3, 4]) + tdser - def test_td64arr_sub_int_series_invalid(self, box_df_broadcast_failure, - tdser): - box = box_df_broadcast_failure + def test_td64arr_sub_int_series_invalid(self, box, tdser): tdser = tm.box_expected(tdser, box) err = TypeError if box is not pd.Index else NullFrequencyError with pytest.raises(err): tdser - Series([2, 3, 4]) - def test_td64arr_rsub_int_series_invalid(self, box_df_broadcast_failure, - tdser): - box = box_df_broadcast_failure + def test_td64arr_rsub_int_series_invalid(self, box, tdser): tdser = tm.box_expected(tdser, box) err = TypeError if box is not pd.Index else NullFrequencyError with pytest.raises(err): @@ -605,9 +597,10 @@ def test_td64arr_add_sub_numeric_scalar_invalid(self, box, scalar, tdser): Series([1, 2, 3]) # TODO: Add DataFrame in here? ], ids=lambda x: type(x).__name__) - def test_td64arr_add_sub_numeric_arr_invalid( - self, box_df_broadcast_failure, vec, dtype, tdser): - box = box_df_broadcast_failure + def test_td64arr_add_sub_numeric_arr_invalid(self, box, vec, dtype, tdser): + if box is pd.DataFrame and not isinstance(vec, Series): + raise pytest.xfail(reason="Tries to broadcast incorrectly") + tdser = tm.box_expected(tdser, box) err = TypeError if box is pd.Index and not dtype.startswith('float'): @@ -930,9 +923,9 @@ def test_td64arr_sub_offset_array(self, box_df_broadcast_failure): @pytest.mark.parametrize('names', [(None, None, None), ('foo', 'bar', None), ('foo', 'foo', 'foo')]) - def test_td64arr_with_offset_series(self, names, box_df_broadcast_failure): + def test_td64arr_with_offset_series(self, names, box_df_fail): # GH#18849 - box = box_df_broadcast_failure + box = box_df_fail box2 = Series if box is pd.Index else box tdi = TimedeltaIndex(['1 days 00:00:00', '3 days 04:00:00'], @@ -963,10 +956,11 @@ def test_td64arr_with_offset_series(self, names, box_df_broadcast_failure): tm.assert_equal(res3, expected_sub) @pytest.mark.parametrize('obox', [np.array, pd.Index, pd.Series]) - def test_td64arr_addsub_anchored_offset_arraylike( - self, obox, box_df_broadcast_failure): + def test_td64arr_addsub_anchored_offset_arraylike(self, obox, box): # GH#18824 - box = box_df_broadcast_failure + if box is pd.DataFrame and obox is not pd.Series: + raise pytest.xfail(reason="Attempts to broadcast incorrectly") + tdi = TimedeltaIndex(['1 days 00:00:00', '3 days 04:00:00']) tdi = tm.box_expected(tdi, box) diff --git a/pandas/tests/frame/test_axis_select_reindex.py b/pandas/tests/frame/test_axis_select_reindex.py index 0bc74c6890ee9e..6186ce4d45ef26 100644 --- a/pandas/tests/frame/test_axis_select_reindex.py +++ b/pandas/tests/frame/test_axis_select_reindex.py @@ -721,7 +721,7 @@ def test_align_int_fill_bug(self): result = df1 - df1.mean() expected = df2 - df2.mean() - assert_frame_equal(result, expected) + assert_frame_equal(result.astype('f8'), expected) def test_align_multiindex(self): # GH 10665 diff --git a/pandas/tests/reshape/test_pivot.py b/pandas/tests/reshape/test_pivot.py index 1ee48d0120c7d0..1cb036dccf23c8 100644 --- a/pandas/tests/reshape/test_pivot.py +++ b/pandas/tests/reshape/test_pivot.py @@ -1566,8 +1566,9 @@ def test_crosstab_normalize(self): full_normal) tm.assert_frame_equal(pd.crosstab(df.a, df.b, normalize='index'), row_normal) - tm.assert_frame_equal(pd.crosstab(df.a, df.b, normalize='columns'), - col_normal) + tm.assert_frame_equal( + pd.crosstab(df.a, df.b, normalize='columns').astype('f8'), + col_normal) tm.assert_frame_equal(pd.crosstab(df.a, df.b, normalize=1), pd.crosstab(df.a, df.b, normalize='columns')) tm.assert_frame_equal(pd.crosstab(df.a, df.b, normalize=0), @@ -1600,7 +1601,8 @@ def test_crosstab_normalize(self): tm.assert_frame_equal(pd.crosstab(df.a, df.b, normalize='index', margins=True), row_normal_margins) tm.assert_frame_equal(pd.crosstab(df.a, df.b, normalize='columns', - margins=True), col_normal_margins) + margins=True).astype('f8'), + col_normal_margins) tm.assert_frame_equal(pd.crosstab(df.a, df.b, normalize=True, margins=True), all_normal_margins) diff --git a/pandas/tests/series/test_operators.py b/pandas/tests/series/test_operators.py index 601e251d45b4b4..f3ab197771d53a 100644 --- a/pandas/tests/series/test_operators.py +++ b/pandas/tests/series/test_operators.py @@ -758,9 +758,6 @@ def test_operators_bitwise(self): def test_scalar_na_cmp_corners(self): s = Series([2, 3, 4, 5, 6, 7, 8, 9, 10]) - def tester(a, b): - return a & b - with pytest.raises(TypeError): s & datetime(2005, 1, 1) @@ -780,8 +777,11 @@ def tester(a, b): # this is an alignment issue; these are equivalent # https://github.com/pandas-dev/pandas/issues/5284 - pytest.raises(ValueError, lambda: d.__and__(s, axis='columns')) - pytest.raises(ValueError, tester, s, d) + with pytest.raises(TypeError): + d.__and__(s, axis='columns') + + with pytest.raises(TypeError): + s & d # this is wrong as its not a boolean result # result = d.__and__(s,axis='index') From fea27f0736a4b8f6626da60a6abc2f6e26b8a365 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 3 Oct 2018 08:49:44 -0500 Subject: [PATCH 64/85] CI: pin moto to 1.3.4 (#22959) --- ci/travis-27.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/travis-27.yaml b/ci/travis-27.yaml index a921bcb46dba4f..6955db363ca1f6 100644 --- a/ci/travis-27.yaml +++ b/ci/travis-27.yaml @@ -44,7 +44,7 @@ dependencies: # universal - pytest - pytest-xdist - - moto + - moto==1.3.4 - hypothesis>=3.58.0 - pip: - backports.lzma From ee808033bd5c546e7439a06d2ed37b57c9e66844 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Wed, 3 Oct 2018 08:25:44 -0700 Subject: [PATCH 65/85] BUG: Correctly weekly resample over DST (#22941) * test resample fix * move the localization until needed * BUG: Correctly weekly resample over DST * Move whatsnew to new section --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/core/resample.py | 16 ++++++++++------ pandas/tests/test_resample.py | 22 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 700916ba6066ea..f246ebad3aa2c6 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -823,6 +823,7 @@ Groupby/Resample/Rolling - Bug in :meth:`Resampler.asfreq` when frequency of ``TimedeltaIndex`` is a subperiod of a new frequency (:issue:`13022`). - Bug in :meth:`SeriesGroupBy.mean` when values were integral but could not fit inside of int64, overflowing instead. (:issue:`22487`) - :func:`RollingGroupby.agg` and :func:`ExpandingGroupby.agg` now support multiple aggregation functions as parameters (:issue:`15072`) +- Bug in :meth:`DataFrame.resample` and :meth:`Series.resample` when resampling by a weekly offset (``'W'``) across a DST transition (:issue:`9119`, :issue:`21459`) Sparse ^^^^^^ diff --git a/pandas/core/resample.py b/pandas/core/resample.py index 878ac957a8557f..70a8deb33b7f27 100644 --- a/pandas/core/resample.py +++ b/pandas/core/resample.py @@ -16,7 +16,8 @@ from pandas.tseries.frequencies import to_offset, is_subperiod, is_superperiod from pandas.core.indexes.datetimes import DatetimeIndex, date_range from pandas.core.indexes.timedeltas import TimedeltaIndex -from pandas.tseries.offsets import DateOffset, Tick, Day, delta_to_nanoseconds +from pandas.tseries.offsets import (DateOffset, Tick, Day, + delta_to_nanoseconds, Nano) from pandas.core.indexes.period import PeriodIndex from pandas.errors import AbstractMethodError import pandas.core.algorithms as algos @@ -1395,18 +1396,21 @@ def _get_time_bins(self, ax): def _adjust_bin_edges(self, binner, ax_values): # Some hacks for > daily data, see #1471, #1458, #1483 - bin_edges = binner.asi8 - if self.freq != 'D' and is_superperiod(self.freq, 'D'): - day_nanos = delta_to_nanoseconds(timedelta(1)) if self.closed == 'right': - bin_edges = bin_edges + day_nanos - 1 + # GH 21459, GH 9119: Adjust the bins relative to the wall time + bin_edges = binner.tz_localize(None) + bin_edges = bin_edges + timedelta(1) - Nano(1) + bin_edges = bin_edges.tz_localize(binner.tz).asi8 + else: + bin_edges = binner.asi8 # intraday values on last day if bin_edges[-2] > ax_values.max(): bin_edges = bin_edges[:-1] binner = binner[:-1] - + else: + bin_edges = binner.asi8 return binner, bin_edges def _get_time_delta_bins(self, ax): diff --git a/pandas/tests/test_resample.py b/pandas/tests/test_resample.py index ccd2461d1512e9..5cd31e08e0a9b2 100644 --- a/pandas/tests/test_resample.py +++ b/pandas/tests/test_resample.py @@ -2114,6 +2114,28 @@ def test_downsample_across_dst(self): freq='H')) tm.assert_series_equal(result, expected) + def test_downsample_across_dst_weekly(self): + # 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'), + data=[11, 12, 13, 14, 15]) + result = df.resample('1W').sum() + expected = DataFrame([23, 42], index=pd.DatetimeIndex([ + '2017-03-26', '2017-04-02' + ], tz='Europe/Amsterdam')) + tm.assert_frame_equal(result, expected) + + idx = pd.date_range("2013-04-01", "2013-05-01", tz='Europe/London', + freq='H') + s = Series(index=idx) + result = s.resample('W').mean() + expected = Series(index=pd.date_range( + '2013-04-07', freq='W', periods=5, tz='Europe/London' + )) + tm.assert_series_equal(result, expected) + def test_resample_with_nat(self): # GH 13020 index = DatetimeIndex([pd.NaT, From c282e310809921a0dadd4446f23c9273c15da443 Mon Sep 17 00:00:00 2001 From: h-vetinari <33685575+h-vetinari@users.noreply.github.com> Date: Thu, 4 Oct 2018 03:34:35 +0200 Subject: [PATCH 66/85] Fix ASV import error (#22978) --- asv_bench/benchmarks/indexing.py | 8 ++++---- asv_bench/benchmarks/join_merge.py | 7 ++++--- asv_bench/benchmarks/panel_ctor.py | 4 ++-- asv_bench/benchmarks/panel_methods.py | 3 ++- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/asv_bench/benchmarks/indexing.py b/asv_bench/benchmarks/indexing.py index c5b147b152aa6e..2850fa249725c7 100644 --- a/asv_bench/benchmarks/indexing.py +++ b/asv_bench/benchmarks/indexing.py @@ -2,10 +2,10 @@ import numpy as np import pandas.util.testing as tm -from pandas import (Series, DataFrame, MultiIndex, Int64Index, Float64Index, - IntervalIndex, CategoricalIndex, - IndexSlice, concat, date_range) -from .pandas_vb_common import setup, Panel # noqa +from pandas import (Series, DataFrame, MultiIndex, Panel, + Int64Index, Float64Index, IntervalIndex, + CategoricalIndex, IndexSlice, concat, date_range) +from .pandas_vb_common import setup # noqa class NumericSeriesIndexing(object): diff --git a/asv_bench/benchmarks/join_merge.py b/asv_bench/benchmarks/join_merge.py index 7487a0d8489b7b..6624c3d0aaf491 100644 --- a/asv_bench/benchmarks/join_merge.py +++ b/asv_bench/benchmarks/join_merge.py @@ -3,14 +3,15 @@ import numpy as np import pandas.util.testing as tm -from pandas import (DataFrame, Series, MultiIndex, date_range, concat, merge, - merge_asof) +from pandas import (DataFrame, Series, Panel, MultiIndex, + date_range, concat, merge, merge_asof) + try: from pandas import merge_ordered except ImportError: from pandas import ordered_merge as merge_ordered -from .pandas_vb_common import Panel, setup # noqa +from .pandas_vb_common import setup # noqa class Append(object): diff --git a/asv_bench/benchmarks/panel_ctor.py b/asv_bench/benchmarks/panel_ctor.py index ce946c76ed1996..4614bbd198afad 100644 --- a/asv_bench/benchmarks/panel_ctor.py +++ b/asv_bench/benchmarks/panel_ctor.py @@ -1,9 +1,9 @@ import warnings from datetime import datetime, timedelta -from pandas import DataFrame, DatetimeIndex, date_range +from pandas import DataFrame, Panel, DatetimeIndex, date_range -from .pandas_vb_common import Panel, setup # noqa +from .pandas_vb_common import setup # noqa class DifferentIndexes(object): diff --git a/asv_bench/benchmarks/panel_methods.py b/asv_bench/benchmarks/panel_methods.py index a5b1a92e9cf679..4d19e9a87c507a 100644 --- a/asv_bench/benchmarks/panel_methods.py +++ b/asv_bench/benchmarks/panel_methods.py @@ -1,8 +1,9 @@ import warnings import numpy as np +from pandas import Panel -from .pandas_vb_common import Panel, setup # noqa +from .pandas_vb_common import setup # noqa class PanelMethods(object): From 9b405b829bf5e3fd142cccbcca46df4cc3df4ccb Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Thu, 4 Oct 2018 13:16:28 +0200 Subject: [PATCH 67/85] CLN: values is required argument in _shallow_copy_with_infer (#22983) --- pandas/core/indexes/base.py | 4 +--- pandas/core/indexes/multi.py | 2 +- pandas/core/indexes/period.py | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index af04a846ed787f..51c84d6e28cb46 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -530,7 +530,7 @@ def _shallow_copy(self, values=None, **kwargs): return self._simple_new(values, **attributes) - def _shallow_copy_with_infer(self, values=None, **kwargs): + def _shallow_copy_with_infer(self, values, **kwargs): """ create a new Index inferring the class with passed value, don't copy the data, use the same object attributes with passed in attributes @@ -543,8 +543,6 @@ def _shallow_copy_with_infer(self, values=None, **kwargs): values : the values to create the new Index, optional kwargs : updates the default attributes for this Index """ - if values is None: - values = self.values attributes = self._get_attributes_dict() attributes.update(kwargs) attributes['copy'] = False diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 6091df776a01b2..3cccb655033781 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -556,7 +556,7 @@ def view(self, cls=None): result._id = self._id return result - def _shallow_copy_with_infer(self, values=None, **kwargs): + def _shallow_copy_with_infer(self, values, **kwargs): # On equal MultiIndexes the difference is empty. # Therefore, an empty MultiIndex is returned GH13490 if len(values) == 0: diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 969391569ce503..cc008694a8b847 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -287,7 +287,7 @@ def _from_ordinals(cls, values, name=None, freq=None, **kwargs): result._reset_identity() return result - def _shallow_copy_with_infer(self, values=None, **kwargs): + def _shallow_copy_with_infer(self, values, **kwargs): """ we always want to return a PeriodIndex """ return self._shallow_copy(values=values, **kwargs) From e6b0c2915f6433d7c29af908f91a6d511177eec1 Mon Sep 17 00:00:00 2001 From: Anjali2019 Date: Thu, 4 Oct 2018 13:20:46 +0200 Subject: [PATCH 68/85] TST: Fixturize series/test_missing.py (#22973) --- pandas/tests/series/test_missing.py | 46 ++++++++++++++--------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/pandas/tests/series/test_missing.py b/pandas/tests/series/test_missing.py index ab3fdd8cbf84f6..b3f105ee5cb678 100644 --- a/pandas/tests/series/test_missing.py +++ b/pandas/tests/series/test_missing.py @@ -21,8 +21,6 @@ import pandas.util.testing as tm import pandas.util._test_decorators as td -from .common import TestData - try: import scipy _is_scipy_ge_0190 = (LooseVersion(scipy.__version__) >= @@ -52,7 +50,7 @@ def _simple_ts(start, end, freq='D'): return Series(np.random.randn(len(rng)), index=rng) -class TestSeriesMissingData(TestData): +class TestSeriesMissingData(): def test_remove_na_deprecation(self): # see gh-16971 @@ -489,7 +487,7 @@ def test_isnull_for_inf_deprecated(self): tm.assert_series_equal(r, e) tm.assert_series_equal(dr, de) - def test_fillna(self): + def test_fillna(self, datetime_series): ts = Series([0., 1., 2., 3., 4.], index=tm.makeDateIndex(5)) tm.assert_series_equal(ts, ts.fillna(method='ffill')) @@ -506,7 +504,8 @@ def test_fillna(self): tm.assert_series_equal(ts.fillna(value=5), exp) pytest.raises(ValueError, ts.fillna) - pytest.raises(ValueError, self.ts.fillna, value=0, method='ffill') + pytest.raises(ValueError, datetime_series.fillna, value=0, + method='ffill') # GH 5703 s1 = Series([np.nan]) @@ -576,9 +575,9 @@ def test_fillna_inplace(self): expected = x.fillna(value=0) assert_series_equal(y, expected) - def test_fillna_invalid_method(self): + def test_fillna_invalid_method(self, datetime_series): try: - self.ts.fillna(method='ffil') + datetime_series.fillna(method='ffil') except ValueError as inst: assert 'ffil' in str(inst) @@ -632,8 +631,8 @@ def test_timedelta64_nan(self): # def test_logical_range_select(self): # np.random.seed(12345) - # selector = -0.5 <= self.ts <= 0.5 - # expected = (self.ts >= -0.5) & (self.ts <= 0.5) + # selector = -0.5 <= datetime_series <= 0.5 + # expected = (datetime_series >= -0.5) & (datetime_series <= 0.5) # assert_series_equal(selector, expected) def test_dropna_empty(self): @@ -688,8 +687,8 @@ def test_dropna_intervals(self): expected = s.iloc[1:] assert_series_equal(result, expected) - def test_valid(self): - ts = self.ts.copy() + def test_valid(self, datetime_series): + ts = datetime_series.copy() ts[::2] = np.NaN result = ts.dropna() @@ -734,12 +733,12 @@ def test_pad_require_monotonicity(self): pytest.raises(ValueError, rng2.get_indexer, rng, method='pad') - def test_dropna_preserve_name(self): - self.ts[:5] = np.nan - result = self.ts.dropna() - assert result.name == self.ts.name - name = self.ts.name - ts = self.ts.copy() + 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() ts.dropna(inplace=True) assert ts.name == name @@ -825,10 +824,11 @@ def test_series_pad_backfill_limit(self): assert_series_equal(result, expected) -class TestSeriesInterpolateData(TestData): +class TestSeriesInterpolateData(): - def test_interpolate(self): - ts = Series(np.arange(len(self.ts), dtype=float), self.ts.index) + def test_interpolate(self, datetime_series, string_series): + ts = Series(np.arange(len(datetime_series), dtype=float), + datetime_series.index) ts_copy = ts.copy() ts_copy[5:10] = np.NaN @@ -836,8 +836,8 @@ def test_interpolate(self): linear_interp = ts_copy.interpolate(method='linear') tm.assert_series_equal(linear_interp, ts) - ord_ts = Series([d.toordinal() for d in self.ts.index], - index=self.ts.index).astype(float) + 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 @@ -847,7 +847,7 @@ def test_interpolate(self): # try time interpolation on a non-TimeSeries # Only raises ValueError if there are NaNs. - non_ts = self.series.copy() + non_ts = string_series.copy() non_ts[0] = np.NaN pytest.raises(ValueError, non_ts.interpolate, method='time') From abf68fd1d5694403e506416c68f6abec6d780c39 Mon Sep 17 00:00:00 2001 From: Anjali2019 Date: Thu, 4 Oct 2018 13:21:45 +0200 Subject: [PATCH 69/85] TST: Fixturize series/test_io.py (#22972) --- pandas/tests/series/test_io.py | 70 +++++++++++++++++----------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/pandas/tests/series/test_io.py b/pandas/tests/series/test_io.py index cbf9bff06ad34c..50f548b855247a 100644 --- a/pandas/tests/series/test_io.py +++ b/pandas/tests/series/test_io.py @@ -16,10 +16,8 @@ assert_frame_equal, ensure_clean) import pandas.util.testing as tm -from .common import TestData - -class TestSeriesToCSV(TestData): +class TestSeriesToCSV(): def read_csv(self, path, **kwargs): params = dict(squeeze=True, index_col=0, @@ -34,10 +32,10 @@ def read_csv(self, path, **kwargs): return out - def test_from_csv_deprecation(self): + def test_from_csv_deprecation(self, datetime_series): # see gh-17812 with ensure_clean() as path: - self.ts.to_csv(path, header=False) + datetime_series.to_csv(path, header=False) with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): @@ -46,7 +44,7 @@ def test_from_csv_deprecation(self): assert_series_equal(depr_ts, ts) @pytest.mark.parametrize("arg", ["path", "header", "both"]) - def test_to_csv_deprecation(self, arg): + def test_to_csv_deprecation(self, arg, datetime_series): # see gh-19715 with ensure_clean() as path: if arg == "path": @@ -57,18 +55,18 @@ def test_to_csv_deprecation(self, arg): kwargs = dict(path=path) with tm.assert_produces_warning(FutureWarning): - self.ts.to_csv(**kwargs) + datetime_series.to_csv(**kwargs) # Make sure roundtrip still works. ts = self.read_csv(path) - assert_series_equal(self.ts, ts, check_names=False) + assert_series_equal(datetime_series, ts, check_names=False) - def test_from_csv(self): + def test_from_csv(self, datetime_series, string_series): with ensure_clean() as path: - self.ts.to_csv(path, header=False) + datetime_series.to_csv(path, header=False) ts = self.read_csv(path) - assert_series_equal(self.ts, ts, check_names=False) + assert_series_equal(datetime_series, ts, check_names=False) assert ts.name is None assert ts.index.name is None @@ -79,18 +77,18 @@ def test_from_csv(self): assert_series_equal(depr_ts, ts) # see gh-10483 - self.ts.to_csv(path, header=True) + datetime_series.to_csv(path, header=True) ts_h = self.read_csv(path, header=0) assert ts_h.name == "ts" - self.series.to_csv(path, header=False) + string_series.to_csv(path, header=False) series = self.read_csv(path) - assert_series_equal(self.series, series, check_names=False) + assert_series_equal(string_series, series, check_names=False) assert series.name is None assert series.index.name is None - self.series.to_csv(path, header=True) + string_series.to_csv(path, header=True) series_h = self.read_csv(path, header=0) assert series_h.name == "series" @@ -106,19 +104,19 @@ def test_from_csv(self): check_series = Series({"1998-01-01": 1.0, "1999-01-01": 2.0}) assert_series_equal(check_series, series) - def test_to_csv(self): + def test_to_csv(self, datetime_series): import io with ensure_clean() as path: - self.ts.to_csv(path, header=False) + datetime_series.to_csv(path, header=False) with io.open(path, newline=None) as f: lines = f.readlines() assert (lines[1] != '\n') - self.ts.to_csv(path, index=False, header=False) + datetime_series.to_csv(path, index=False, header=False) arr = np.loadtxt(path) - assert_almost_equal(arr, self.ts.values) + assert_almost_equal(arr, datetime_series.values) def test_to_csv_unicode_index(self): buf = StringIO() @@ -196,22 +194,23 @@ def test_to_csv_compression(self, s, encoding, compression): encoding=encoding)) -class TestSeriesIO(TestData): +class TestSeriesIO(): - def test_to_frame(self): - self.ts.name = None - rs = self.ts.to_frame() - xp = pd.DataFrame(self.ts.values, index=self.ts.index) + def test_to_frame(self, datetime_series): + datetime_series.name = None + rs = datetime_series.to_frame() + xp = pd.DataFrame(datetime_series.values, index=datetime_series.index) assert_frame_equal(rs, xp) - self.ts.name = 'testname' - rs = self.ts.to_frame() - xp = pd.DataFrame(dict(testname=self.ts.values), index=self.ts.index) + datetime_series.name = 'testname' + rs = datetime_series.to_frame() + xp = pd.DataFrame(dict(testname=datetime_series.values), + index=datetime_series.index) assert_frame_equal(rs, xp) - rs = self.ts.to_frame(name='testdifferent') - xp = pd.DataFrame( - dict(testdifferent=self.ts.values), index=self.ts.index) + rs = datetime_series.to_frame(name='testdifferent') + xp = pd.DataFrame(dict(testdifferent=datetime_series.values), + index=datetime_series.index) assert_frame_equal(rs, xp) def test_timeseries_periodindex(self): @@ -256,11 +255,12 @@ class SubclassedFrame(DataFrame): dict, collections.defaultdict(list), collections.OrderedDict)) - def test_to_dict(self, mapping): + def test_to_dict(self, mapping, datetime_series): # GH16122 - ts = TestData().ts tm.assert_series_equal( - Series(ts.to_dict(mapping), name='ts'), ts) - from_method = Series(ts.to_dict(collections.Counter)) - from_constructor = Series(collections.Counter(ts.iteritems())) + Series(datetime_series.to_dict(mapping), name='ts'), + datetime_series) + from_method = Series(datetime_series.to_dict(collections.Counter)) + from_constructor = Series(collections + .Counter(datetime_series.iteritems())) tm.assert_series_equal(from_method, from_constructor) From f1a22ff56f895ed340ed7db6dc46841b81d331a1 Mon Sep 17 00:00:00 2001 From: Anjali2019 Date: Thu, 4 Oct 2018 13:22:21 +0200 Subject: [PATCH 70/85] TST: Fixturize series/test_dtypes.py (#22967) --- pandas/tests/series/test_dtypes.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/pandas/tests/series/test_dtypes.py b/pandas/tests/series/test_dtypes.py index 125dff9ecfa7c7..63ead2dc7d245c 100644 --- a/pandas/tests/series/test_dtypes.py +++ b/pandas/tests/series/test_dtypes.py @@ -24,10 +24,8 @@ from pandas import compat import pandas.util.testing as tm -from .common import TestData - -class TestSeriesDtypes(TestData): +class TestSeriesDtypes(): def test_dt64_series_astype_object(self): dt64ser = Series(date_range('20130101', periods=3)) @@ -56,17 +54,17 @@ def test_asobject_deprecated(self): o = s.asobject assert isinstance(o, np.ndarray) - def test_dtype(self): + def test_dtype(self, datetime_series): - assert self.ts.dtype == np.dtype('float64') - assert self.ts.dtypes == np.dtype('float64') - assert self.ts.ftype == 'float64:dense' - assert self.ts.ftypes == 'float64:dense' - tm.assert_series_equal(self.ts.get_dtype_counts(), + assert datetime_series.dtype == np.dtype('float64') + assert datetime_series.dtypes == np.dtype('float64') + assert datetime_series.ftype == 'float64:dense' + assert datetime_series.ftypes == 'float64:dense' + tm.assert_series_equal(datetime_series.get_dtype_counts(), Series(1, ['float64'])) # GH18243 - Assert .get_ftype_counts is deprecated with tm.assert_produces_warning(FutureWarning): - tm.assert_series_equal(self.ts.get_ftype_counts(), + tm.assert_series_equal(datetime_series.get_ftype_counts(), Series(1, ['float64:dense'])) @pytest.mark.parametrize("value", [np.nan, np.inf]) From 45d3bb761dd44edd0853b06fd81f05af915fd695 Mon Sep 17 00:00:00 2001 From: Anjali2019 Date: Thu, 4 Oct 2018 13:23:20 +0200 Subject: [PATCH 71/85] TST: Fixturize series/test_datetime_values.py (#22966) --- pandas/tests/series/test_datetime_values.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pandas/tests/series/test_datetime_values.py b/pandas/tests/series/test_datetime_values.py index fee2323310b9c5..e06d3a67db6625 100644 --- a/pandas/tests/series/test_datetime_values.py +++ b/pandas/tests/series/test_datetime_values.py @@ -23,10 +23,8 @@ from pandas.util.testing import assert_series_equal import pandas.util.testing as tm -from .common import TestData - -class TestSeriesDatetimeValues(TestData): +class TestSeriesDatetimeValues(): def test_dt_namespace_accessor(self): From 4c78b9738e01ae147106301cca76c6b36ee68d06 Mon Sep 17 00:00:00 2001 From: Anjali2019 Date: Thu, 4 Oct 2018 13:23:39 +0200 Subject: [PATCH 72/85] TST: Fixturize series/test_constructors.py (#22965) --- pandas/tests/series/test_constructors.py | 36 +++++++++++------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 4817f5bdccc29c..57a3f54fadbcc4 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -26,10 +26,8 @@ from pandas.util.testing import assert_series_equal import pandas.util.testing as tm -from .common import TestData - -class TestSeriesConstructors(TestData): +class TestSeriesConstructors(): def test_invalid_dtype(self): # GH15520 @@ -50,23 +48,23 @@ def test_scalar_conversion(self): assert int(Series([1.])) == 1 assert long(Series([1.])) == 1 - def test_constructor(self): - assert self.ts.index.is_all_dates + def test_constructor(self, datetime_series, empty_series): + assert datetime_series.index.is_all_dates # Pass in Series - derived = Series(self.ts) + derived = Series(datetime_series) assert derived.index.is_all_dates - assert tm.equalContents(derived.index, self.ts.index) + assert tm.equalContents(derived.index, datetime_series.index) # Ensure new index is not created - assert id(self.ts.index) == id(derived.index) + assert id(datetime_series.index) == id(derived.index) # Mixed type Series mixed = Series(['hello', np.NaN], index=[0, 1]) assert mixed.dtype == np.object_ assert mixed[1] is np.NaN - assert not self.empty.index.is_all_dates + assert not empty_series.index.is_all_dates assert not Series({}).index.is_all_dates pytest.raises(Exception, Series, np.random.randn(3, 3), index=np.arange(3)) @@ -977,27 +975,27 @@ def test_fromDict(self): series = Series(data, dtype=float) assert series.dtype == np.float64 - def test_fromValue(self): + def test_fromValue(self, datetime_series): - nans = Series(np.NaN, index=self.ts.index) + nans = Series(np.NaN, index=datetime_series.index) assert nans.dtype == np.float_ - assert len(nans) == len(self.ts) + assert len(nans) == len(datetime_series) - strings = Series('foo', index=self.ts.index) + strings = Series('foo', index=datetime_series.index) assert strings.dtype == np.object_ - assert len(strings) == len(self.ts) + assert len(strings) == len(datetime_series) d = datetime.now() - dates = Series(d, index=self.ts.index) + dates = Series(d, index=datetime_series.index) assert dates.dtype == 'M8[ns]' - assert len(dates) == len(self.ts) + assert len(dates) == len(datetime_series) # GH12336 # Test construction of categorical series from value - categorical = Series(0, index=self.ts.index, dtype="category") - expected = Series(0, index=self.ts.index).astype("category") + 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(self.ts) + assert len(categorical) == len(datetime_series) tm.assert_series_equal(categorical, expected) def test_constructor_dtype_timedelta64(self): From d553ab3e5650d105de8e02ae6fd57d03af57b214 Mon Sep 17 00:00:00 2001 From: Anjali2019 Date: Thu, 4 Oct 2018 13:24:06 +0200 Subject: [PATCH 73/85] TST: Fixturize series/test_combine_concat.py (#22964) --- pandas/tests/series/test_combine_concat.py | 25 +++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/pandas/tests/series/test_combine_concat.py b/pandas/tests/series/test_combine_concat.py index 35ba4fbf0ce25f..8b021ab81ff816 100644 --- a/pandas/tests/series/test_combine_concat.py +++ b/pandas/tests/series/test_combine_concat.py @@ -15,29 +15,28 @@ from pandas.util.testing import assert_series_equal import pandas.util.testing as tm -from .common import TestData +class TestSeriesCombine(): -class TestSeriesCombine(TestData): - - def test_append(self): - appendedSeries = self.series.append(self.objSeries) + def test_append(self, datetime_series, string_series, object_series): + appendedSeries = string_series.append(object_series) for idx, value in compat.iteritems(appendedSeries): - if idx in self.series.index: - assert value == self.series[idx] - elif idx in self.objSeries.index: - assert value == self.objSeries[idx] + if idx in string_series.index: + assert value == string_series[idx] + elif idx in object_series.index: + assert value == object_series[idx] else: raise AssertionError("orphaned index!") - pytest.raises(ValueError, self.ts.append, self.ts, + pytest.raises(ValueError, datetime_series.append, datetime_series, verify_integrity=True) - def test_append_many(self): - pieces = [self.ts[:5], self.ts[5:10], self.ts[10:]] + def test_append_many(self, datetime_series): + pieces = [datetime_series[:5], datetime_series[5:10], + datetime_series[10:]] result = pieces[0].append(pieces[1:]) - assert_series_equal(result, self.ts) + assert_series_equal(result, datetime_series) def test_append_duplicates(self): # GH 13677 From c19c8052f384206c3b2cd87f277344d21d0ae2c7 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 4 Oct 2018 06:27:54 -0500 Subject: [PATCH 74/85] Catch Exception in combine (#22936) --- pandas/core/arrays/base.py | 26 +++++++--- pandas/core/series.py | 8 ++- pandas/tests/extension/decimal/__init__.py | 4 ++ pandas/tests/extension/decimal/array.py | 9 ++++ .../tests/extension/decimal/test_decimal.py | 52 ++++++++++++++++--- pandas/tests/extension/json/__init__.py | 3 ++ pandas/tests/extension/json/array.py | 9 ++++ pandas/tests/extension/json/test_json.py | 11 +--- 8 files changed, 97 insertions(+), 25 deletions(-) diff --git a/pandas/core/arrays/base.py b/pandas/core/arrays/base.py index 7bf13fb2fecc02..f7c4ee35adfe48 100644 --- a/pandas/core/arrays/base.py +++ b/pandas/core/arrays/base.py @@ -739,14 +739,22 @@ def _create_method(cls, op, coerce_to_dtype=True): ---------- op : function An operator that takes arguments op(a, b) - coerce_to_dtype : bool + coerce_to_dtype : bool, default True boolean indicating whether to attempt to convert - the result to the underlying ExtensionArray dtype - (default True) + the result to the underlying ExtensionArray dtype. + If it's not possible to create a new ExtensionArray with the + values, an ndarray is returned instead. Returns ------- - A method that can be bound to a method of a class + Callable[[Any, Any], Union[ndarray, ExtensionArray]] + A method that can be bound to a class. When used, the method + receives the two arguments, one of which is the instance of + this class, and should return an ExtensionArray or an ndarray. + + Returning an ndarray may be necessary when the result of the + `op` cannot be stored in the ExtensionArray. The dtype of the + ndarray uses NumPy's normal inference rules. Example ------- @@ -757,7 +765,6 @@ def _create_method(cls, op, coerce_to_dtype=True): in the class definition of MyExtensionArray to create the operator for addition, that will be based on the operator implementation of the underlying elements of the ExtensionArray - """ def _binop(self, other): @@ -777,8 +784,13 @@ def convert_values(param): if coerce_to_dtype: try: res = self._from_sequence(res) - except TypeError: - pass + except Exception: + # https://github.com/pandas-dev/pandas/issues/22850 + # We catch all regular exceptions here, and fall back + # to an ndarray. + res = np.asarray(res) + else: + res = np.asarray(res) return res diff --git a/pandas/core/series.py b/pandas/core/series.py index 82198c2b3edd5e..2e22e4e6e1bfc9 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -2323,10 +2323,14 @@ def combine(self, other, func, fill_value=None): pass elif is_extension_array_dtype(self.values): # The function can return something of any type, so check - # if the type is compatible with the calling EA + # if the type is compatible with the calling EA. try: new_values = self._values._from_sequence(new_values) - except TypeError: + except Exception: + # https://github.com/pandas-dev/pandas/issues/22850 + # pandas has no control over what 3rd-party ExtensionArrays + # do in _values_from_sequence. We still want ops to work + # though, so we catch any regular Exception. pass return self._constructor(new_values, index=new_index, name=new_name) diff --git a/pandas/tests/extension/decimal/__init__.py b/pandas/tests/extension/decimal/__init__.py index e69de29bb2d1d6..c37aad0af84075 100644 --- a/pandas/tests/extension/decimal/__init__.py +++ b/pandas/tests/extension/decimal/__init__.py @@ -0,0 +1,4 @@ +from .array import DecimalArray, DecimalDtype, to_decimal, make_data + + +__all__ = ['DecimalArray', 'DecimalDtype', 'to_decimal', 'make_data'] diff --git a/pandas/tests/extension/decimal/array.py b/pandas/tests/extension/decimal/array.py index 387942234e6fd4..79e1a692f744ac 100644 --- a/pandas/tests/extension/decimal/array.py +++ b/pandas/tests/extension/decimal/array.py @@ -1,5 +1,6 @@ import decimal import numbers +import random import sys import numpy as np @@ -138,5 +139,13 @@ def _concat_same_type(cls, to_concat): return cls(np.concatenate([x._data for x in to_concat])) +def to_decimal(values, context=None): + return DecimalArray([decimal.Decimal(x) for x in values], context=context) + + +def make_data(): + return [decimal.Decimal(random.random()) for _ in range(100)] + + DecimalArray._add_arithmetic_ops() DecimalArray._add_comparison_ops() diff --git a/pandas/tests/extension/decimal/test_decimal.py b/pandas/tests/extension/decimal/test_decimal.py index 93b8ea786ef5bc..dd625d6e1eb3cf 100644 --- a/pandas/tests/extension/decimal/test_decimal.py +++ b/pandas/tests/extension/decimal/test_decimal.py @@ -1,6 +1,6 @@ +import operator import decimal -import random import numpy as np import pandas as pd import pandas.util.testing as tm @@ -8,11 +8,7 @@ from pandas.tests.extension import base -from .array import DecimalDtype, DecimalArray - - -def make_data(): - return [decimal.Decimal(random.random()) for _ in range(100)] +from .array import DecimalDtype, DecimalArray, make_data @pytest.fixture @@ -275,3 +271,47 @@ def test_compare_array(self, data, all_compare_operators): other = pd.Series(data) * [decimal.Decimal(pow(2.0, i)) for i in alter] self._compare_other(s, data, op_name, other) + + +class DecimalArrayWithoutFromSequence(DecimalArray): + """Helper class for testing error handling in _from_sequence.""" + 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_combine_from_sequence_raises(): + # https://github.com/pandas-dev/pandas/issues/22850 + ser = pd.Series(DecimalArrayWithoutFromSequence([ + decimal.Decimal("1.0"), + decimal.Decimal("2.0") + ])) + result = ser.combine(ser, operator.add) + + # note: object dtype + expected = pd.Series([decimal.Decimal("2.0"), + decimal.Decimal("4.0")], dtype="object") + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize("class_", [DecimalArrayWithoutFromSequence, + DecimalArrayWithoutCoercion]) +def test_scalar_ops_from_sequence_raises(class_): + # op(EA, EA) should return an EA, or an ndarray if it's not possible + # to return an EA with the return values. + arr = class_([ + 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) diff --git a/pandas/tests/extension/json/__init__.py b/pandas/tests/extension/json/__init__.py index e69de29bb2d1d6..f2679d087c8410 100644 --- a/pandas/tests/extension/json/__init__.py +++ b/pandas/tests/extension/json/__init__.py @@ -0,0 +1,3 @@ +from .array import JSONArray, JSONDtype, make_data + +__all__ = ['JSONArray', 'JSONDtype', 'make_data'] diff --git a/pandas/tests/extension/json/array.py b/pandas/tests/extension/json/array.py index 6ce0d63eb63ec4..87876d84bef995 100644 --- a/pandas/tests/extension/json/array.py +++ b/pandas/tests/extension/json/array.py @@ -13,6 +13,8 @@ import collections import itertools import numbers +import random +import string import sys import numpy as np @@ -179,3 +181,10 @@ def _values_for_argsort(self): # cast them to an (N, P) array, instead of an (N,) array of tuples. frozen = [()] + [tuple(x.items()) for x in self] return np.array(frozen, dtype=object)[1:] + + +def make_data(): + # TODO: Use a regular dict. See _NDFrameIndexer._setitem_with_indexer + return [collections.UserDict([ + (random.choice(string.ascii_letters), random.randint(0, 100)) + for _ in range(random.randint(0, 10))]) for _ in range(100)] diff --git a/pandas/tests/extension/json/test_json.py b/pandas/tests/extension/json/test_json.py index 93f10b7fbfc232..bcbc3e91091821 100644 --- a/pandas/tests/extension/json/test_json.py +++ b/pandas/tests/extension/json/test_json.py @@ -1,7 +1,5 @@ import operator import collections -import random -import string import pytest @@ -10,18 +8,11 @@ from pandas.compat import PY2, PY36 from pandas.tests.extension import base -from .array import JSONArray, JSONDtype +from .array import JSONArray, JSONDtype, make_data pytestmark = pytest.mark.skipif(PY2, reason="Py2 doesn't have a UserDict") -def make_data(): - # TODO: Use a regular dict. See _NDFrameIndexer._setitem_with_indexer - return [collections.UserDict([ - (random.choice(string.ascii_letters), random.randint(0, 100)) - for _ in range(random.randint(0, 10))]) for _ in range(100)] - - @pytest.fixture def dtype(): return JSONDtype() From b12e5ba55c3691733dab36373e80d1b16134c8c2 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 4 Oct 2018 06:30:29 -0500 Subject: [PATCH 75/85] Safer is dtype (#22975) --- .travis.yml | 19 +++++++------------ pandas/core/dtypes/base.py | 8 +++++++- pandas/core/frame.py | 3 ++- pandas/core/series.py | 2 +- pandas/tests/dtypes/test_dtypes.py | 20 ++++++++++++++++++++ pandas/tests/frame/test_operators.py | 6 ++++++ 6 files changed, 43 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 40baee2c03ea00..c9bdb91283d426 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,11 +53,7 @@ matrix: - dist: trusty env: - JOB="3.6, coverage" ENV_FILE="ci/travis-36.yaml" TEST_ARGS="--skip-slow --skip-network" PANDAS_TESTING_MODE="deprecate" COVERAGE=true DOCTEST=true - # In allow_failures - - dist: trusty - env: - - JOB="3.6, slow" ENV_FILE="ci/travis-36-slow.yaml" SLOW=true - # In allow_failures + - dist: trusty env: - JOB="3.7, NumPy dev" ENV_FILE="ci/travis-37-numpydev.yaml" TEST_ARGS="--skip-slow --skip-network -W error" PANDAS_TESTING_MODE="deprecate" @@ -65,6 +61,12 @@ matrix: apt: packages: - xsel + + # In allow_failures + - dist: trusty + env: + - JOB="3.6, slow" ENV_FILE="ci/travis-36-slow.yaml" SLOW=true + # In allow_failures - dist: trusty env: @@ -73,13 +75,6 @@ matrix: - dist: trusty env: - JOB="3.6, slow" ENV_FILE="ci/travis-36-slow.yaml" SLOW=true - - dist: trusty - env: - - JOB="3.7, NumPy dev" ENV_FILE="ci/travis-37-numpydev.yaml" TEST_ARGS="--skip-slow --skip-network -W error" PANDAS_TESTING_MODE="deprecate" - addons: - apt: - packages: - - xsel - dist: trusty env: - JOB="3.6, doc" ENV_FILE="ci/travis-36-doc.yaml" DOC=true diff --git a/pandas/core/dtypes/base.py b/pandas/core/dtypes/base.py index a552251ebbafa0..db0a917aefb85d 100644 --- a/pandas/core/dtypes/base.py +++ b/pandas/core/dtypes/base.py @@ -2,6 +2,7 @@ import numpy as np from pandas import compat +from pandas.core.dtypes.generic import ABCSeries, ABCIndexClass, ABCDataFrame from pandas.errors import AbstractMethodError @@ -83,7 +84,12 @@ def is_dtype(cls, dtype): """ dtype = getattr(dtype, 'dtype', dtype) - if isinstance(dtype, np.dtype): + if isinstance(dtype, (ABCSeries, ABCIndexClass, + ABCDataFrame, np.dtype)): + # https://github.com/pandas-dev/pandas/issues/22960 + # avoid passing data to `construct_from_string`. This could + # cause a FutureWarning from numpy about failing elementwise + # comparison from, e.g., comparing DataFrame == 'category'. return False elif dtype is None: return False diff --git a/pandas/core/frame.py b/pandas/core/frame.py index ff7590f6d53582..f4b7ccb0fdf5bf 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -4908,7 +4908,8 @@ def _combine_match_index(self, other, func, level=None): return ops.dispatch_to_series(left, right, func) else: # fastpath --> operate directly on values - new_data = func(left.values.T, right.values).T + with np.errstate(all="ignore"): + new_data = func(left.values.T, right.values).T return self._constructor(new_data, index=left.index, columns=self.columns, copy=False) diff --git a/pandas/core/series.py b/pandas/core/series.py index 2e22e4e6e1bfc9..a613b22ea90467 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -4228,7 +4228,7 @@ def _try_cast(arr, take_fast_path): try: # gh-15832: Check if we are requesting a numeric dype and # that we can convert the data to the requested dtype. - if is_float_dtype(dtype) or is_integer_dtype(dtype): + if is_integer_dtype(dtype): subarr = maybe_cast_to_integer_array(arr, dtype) subarr = maybe_cast_to_datetime(arr, dtype) diff --git a/pandas/tests/dtypes/test_dtypes.py b/pandas/tests/dtypes/test_dtypes.py index e3d14497a38f93..7e95b076a8a665 100644 --- a/pandas/tests/dtypes/test_dtypes.py +++ b/pandas/tests/dtypes/test_dtypes.py @@ -815,3 +815,23 @@ def test_registry_find(dtype, expected): ('datetime64[ns, US/Eastern]', DatetimeTZDtype('ns', 'US/Eastern'))]) def test_pandas_registry_find(dtype, expected): assert _pandas_registry.find(dtype) == expected + + +@pytest.mark.parametrize("check", [ + is_categorical_dtype, + is_datetime64tz_dtype, + is_period_dtype, + is_datetime64_ns_dtype, + is_datetime64_dtype, + is_interval_dtype, + is_datetime64_any_dtype, + is_string_dtype, + is_bool_dtype, +]) +def test_is_dtype_no_warning(check): + data = pd.DataFrame({"A": [1, 2]}) + with tm.assert_produces_warning(None): + check(data) + + with tm.assert_produces_warning(None): + check(data["A"]) diff --git a/pandas/tests/frame/test_operators.py b/pandas/tests/frame/test_operators.py index 97c94e1134cc88..6ed289614b96a4 100644 --- a/pandas/tests/frame/test_operators.py +++ b/pandas/tests/frame/test_operators.py @@ -1030,3 +1030,9 @@ def test_alignment_non_pandas(self): align(df, val, 'index') with pytest.raises(ValueError): align(df, val, 'columns') + + def test_no_warning(self, all_arithmetic_operators): + df = pd.DataFrame({"A": [0., 0.], "B": [0., None]}) + b = df['B'] + with tm.assert_produces_warning(None): + getattr(df, all_arithmetic_operators)(b, 0) From fe67b94e7681c1f21fc2be212514ca0d67a6603c Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 4 Oct 2018 06:55:09 -0500 Subject: [PATCH 76/85] Update type for PeriodDtype / DatetimeTZDtype / IntervalDtype (#22938) --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/core/dtypes/base.py | 4 +++- pandas/core/dtypes/common.py | 19 ++++++++-------- pandas/core/dtypes/dtypes.py | 36 +++++++++--------------------- pandas/tests/dtypes/test_common.py | 12 +++++----- 5 files changed, 30 insertions(+), 42 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index f246ebad3aa2c6..c9874b4dd03d65 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -505,6 +505,7 @@ ExtensionType Changes - :meth:`Series.astype` and :meth:`DataFrame.astype` now dispatch to :meth:`ExtensionArray.astype` (:issue:`21185:`). - Slicing a single row of a ``DataFrame`` with multiple ExtensionArrays of the same type now preserves the dtype, rather than coercing to object (:issue:`22784`) - Added :meth:`pandas.api.types.register_extension_dtype` to register an extension type with pandas (:issue:`22664`) +- Updated the ``.type`` attribute for ``PeriodDtype``, ``DatetimeTZDtype``, and ``IntervalDtype`` to be instances of the dtype (``Period``, ``Timestamp``, and ``Interval`` respectively) (:issue:`22938`) .. _whatsnew_0240.api.incompatibilities: diff --git a/pandas/core/dtypes/base.py b/pandas/core/dtypes/base.py index db0a917aefb85d..b0fa55e3466135 100644 --- a/pandas/core/dtypes/base.py +++ b/pandas/core/dtypes/base.py @@ -181,7 +181,9 @@ def type(self): """The scalar type for the array, e.g. ``int`` It's expected ``ExtensionArray[item]`` returns an instance - of ``ExtensionDtype.type`` for scalar ``item``. + of ``ExtensionDtype.type`` for scalar ``item``, assuming + that value is valid (not NA). NA values do not need to be + instances of `type`. """ raise AbstractMethodError(self) diff --git a/pandas/core/dtypes/common.py b/pandas/core/dtypes/common.py index 5f0b71d4505c27..a9fc9d13d4ab3c 100644 --- a/pandas/core/dtypes/common.py +++ b/pandas/core/dtypes/common.py @@ -4,12 +4,13 @@ from pandas.compat import (string_types, text_type, binary_type, PY3, PY36) from pandas._libs import algos, lib -from pandas._libs.tslibs import conversion +from pandas._libs.tslibs import conversion, Period, Timestamp +from pandas._libs.interval import Interval from pandas.core.dtypes.dtypes import ( registry, CategoricalDtype, CategoricalDtypeType, DatetimeTZDtype, - DatetimeTZDtypeType, PeriodDtype, PeriodDtypeType, IntervalDtype, - IntervalDtypeType, PandasExtensionDtype, ExtensionDtype, + PeriodDtype, IntervalDtype, + PandasExtensionDtype, ExtensionDtype, _pandas_registry) from pandas.core.dtypes.generic import ( ABCCategorical, ABCPeriodIndex, ABCDatetimeIndex, ABCSeries, @@ -1905,20 +1906,20 @@ def _get_dtype_type(arr_or_dtype): elif isinstance(arr_or_dtype, CategoricalDtype): return CategoricalDtypeType elif isinstance(arr_or_dtype, DatetimeTZDtype): - return DatetimeTZDtypeType + return Timestamp elif isinstance(arr_or_dtype, IntervalDtype): - return IntervalDtypeType + return Interval elif isinstance(arr_or_dtype, PeriodDtype): - return PeriodDtypeType + return Period elif isinstance(arr_or_dtype, string_types): if is_categorical_dtype(arr_or_dtype): return CategoricalDtypeType elif is_datetime64tz_dtype(arr_or_dtype): - return DatetimeTZDtypeType + return Timestamp elif is_period_dtype(arr_or_dtype): - return PeriodDtypeType + return Period elif is_interval_dtype(arr_or_dtype): - return IntervalDtypeType + return Interval return _get_dtype_type(np.dtype(arr_or_dtype)) try: return arr_or_dtype.dtype.type diff --git a/pandas/core/dtypes/dtypes.py b/pandas/core/dtypes/dtypes.py index fe5cc9389a8baf..beda9bc02f4d58 100644 --- a/pandas/core/dtypes/dtypes.py +++ b/pandas/core/dtypes/dtypes.py @@ -4,6 +4,8 @@ import numpy as np from pandas import compat from pandas.core.dtypes.generic import ABCIndexClass, ABCCategoricalIndex +from pandas._libs.tslibs import Period, NaT, Timestamp +from pandas._libs.interval import Interval from .base import ExtensionDtype, _DtypeOpsMixin @@ -469,13 +471,6 @@ def _is_boolean(self): return is_bool_dtype(self.categories) -class DatetimeTZDtypeType(type): - """ - the type of DatetimeTZDtype, this metaclass determines subclass ability - """ - pass - - class DatetimeTZDtype(PandasExtensionDtype): """ @@ -485,7 +480,7 @@ class DatetimeTZDtype(PandasExtensionDtype): THIS IS NOT A REAL NUMPY DTYPE, but essentially a sub-class of np.datetime64[ns] """ - type = DatetimeTZDtypeType + type = Timestamp kind = 'M' str = '|M8[ns]' num = 101 @@ -583,20 +578,13 @@ def __eq__(self, other): str(self.tz) == str(other.tz)) -class PeriodDtypeType(type): - """ - the type of PeriodDtype, this metaclass determines subclass ability - """ - pass - - class PeriodDtype(PandasExtensionDtype): """ A Period duck-typed class, suitable for holding a period with freq dtype. THIS IS NOT A REAL NUMPY DTYPE, but essentially a sub-class of np.int64. """ - type = PeriodDtypeType + type = Period kind = 'O' str = '|O08' base = np.dtype('O') @@ -666,11 +654,15 @@ def construct_from_string(cls, string): raise TypeError("could not construct PeriodDtype") def __unicode__(self): - return "period[{freq}]".format(freq=self.freq.freqstr) + return compat.text_type(self.name) @property def name(self): - return str(self) + return str("period[{freq}]".format(freq=self.freq.freqstr)) + + @property + def na_value(self): + return NaT def __hash__(self): # make myself hashable @@ -705,13 +697,6 @@ def is_dtype(cls, dtype): return super(PeriodDtype, cls).is_dtype(dtype) -class IntervalDtypeType(type): - """ - the type of IntervalDtype, this metaclass determines subclass ability - """ - pass - - @register_extension_dtype class IntervalDtype(PandasExtensionDtype, ExtensionDtype): """ @@ -800,7 +785,6 @@ def construct_from_string(cls, string): @property def type(self): - from pandas import Interval return Interval def __unicode__(self): diff --git a/pandas/tests/dtypes/test_common.py b/pandas/tests/dtypes/test_common.py index a7a9faa9e77ebd..f87c51a4ee16b9 100644 --- a/pandas/tests/dtypes/test_common.py +++ b/pandas/tests/dtypes/test_common.py @@ -605,15 +605,15 @@ def test__get_dtype_fails(input_param): (pd.DatetimeIndex([1, 2]), np.datetime64), (pd.DatetimeIndex([1, 2]).dtype, np.datetime64), (' Date: Thu, 4 Oct 2018 10:15:57 -0500 Subject: [PATCH 77/85] CI: Pin IPYthon for doc build (#22991) --- ci/travis-36-doc.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/travis-36-doc.yaml b/ci/travis-36-doc.yaml index 50626088d5bc49..8353659e7b9a95 100644 --- a/ci/travis-36-doc.yaml +++ b/ci/travis-36-doc.yaml @@ -12,7 +12,7 @@ dependencies: - html5lib - hypothesis>=3.58.0 - ipykernel - - ipython + - ipython==6.5.0 - ipywidgets - lxml - matplotlib From d430195ffaa37df2d8fc3ecf26306bb5e06b4ad0 Mon Sep 17 00:00:00 2001 From: Jesper Dramsch Date: Thu, 4 Oct 2018 20:55:59 +0200 Subject: [PATCH 78/85] DOC: Updating str_slice docstring (#22569) --- pandas/core/strings.py | 62 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/pandas/core/strings.py b/pandas/core/strings.py index 861739f6c694c0..5a23951145cb48 100644 --- a/pandas/core/strings.py +++ b/pandas/core/strings.py @@ -1437,17 +1437,69 @@ def str_rsplit(arr, pat=None, n=None): def str_slice(arr, start=None, stop=None, step=None): """ - Slice substrings from each element in the Series/Index + Slice substrings from each element in the Series or Index. Parameters ---------- - start : int or None - stop : int or None - step : int or None + start : int, optional + Start position for slice operation. + stop : int, optional + Stop position for slice operation. + step : int, optional + Step size for slice operation. Returns ------- - sliced : Series/Index of objects + Series or Index of object + Series or Index from sliced substring from original string object. + + See Also + -------- + Series.str.slice_replace : Replace a slice with a string. + Series.str.get : Return element at position. + Equivalent to `Series.str.slice(start=i, stop=i+1)` with `i` + being the position. + + Examples + -------- + >>> s = pd.Series(["koala", "fox", "chameleon"]) + >>> s + 0 koala + 1 fox + 2 chameleon + dtype: object + + >>> s.str.slice(start=1) + 0 oala + 1 ox + 2 hameleon + dtype: object + + >>> s.str.slice(stop=2) + 0 ko + 1 fo + 2 ch + dtype: object + + >>> s.str.slice(step=2) + 0 kaa + 1 fx + 2 caeen + dtype: object + + >>> s.str.slice(start=0, stop=5, step=3) + 0 kl + 1 f + 2 cm + dtype: object + + Equivalent behaviour to: + + >>> s.str[0:5:3] + 0 kl + 1 f + 2 cm + dtype: object """ obj = slice(start, stop, step) f = lambda x: x[obj] From 8af8cdbc251c6268fc02a5af9b18e9196a9b6067 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Thu, 4 Oct 2018 14:53:44 -0700 Subject: [PATCH 79/85] use tm.assert_equal instead of parametrizing assert funcs (#22995) * Use tm.assert_equal instead of parametrizing assert_func * Extend assert_equal * Use tm.assert_equal in more places * typo fixup --- doc/source/contributing.rst | 2 +- pandas/tests/indexes/datetimes/test_tools.py | 22 +++++++-------- pandas/tests/scalar/test_nat.py | 15 +++++------ pandas/tests/tseries/offsets/test_offsets.py | 28 +++++++++----------- pandas/util/testing.py | 8 ++++-- 5 files changed, 35 insertions(+), 40 deletions(-) diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst index 65e151feeba678..445f9a7e5e9804 100644 --- a/doc/source/contributing.rst +++ b/doc/source/contributing.rst @@ -880,7 +880,7 @@ If your change involves checking that a warning is actually emitted, use .. code-block:: python - with tm.assert_prodcues_warning(FutureWarning): + with tm.assert_produces_warning(FutureWarning): df.some_operation() We prefer this to the ``pytest.warns`` context manager because ours checks that the warning's diff --git a/pandas/tests/indexes/datetimes/test_tools.py b/pandas/tests/indexes/datetimes/test_tools.py index 3b7d6a709230bd..74703e2837c4ab 100644 --- a/pandas/tests/indexes/datetimes/test_tools.py +++ b/pandas/tests/indexes/datetimes/test_tools.py @@ -180,9 +180,9 @@ def test_to_datetime_format_weeks(self, cache): for s, format, dt in data: assert to_datetime(s, format=format, cache=cache) == dt - @pytest.mark.parametrize("box,const,assert_equal", [ - [True, pd.Index, 'assert_index_equal'], - [False, np.array, 'assert_numpy_array_equal']]) + @pytest.mark.parametrize("box,const", [ + [True, pd.Index], + [False, np.array]]) @pytest.mark.parametrize("fmt,dates,expected_dates", [ ['%Y-%m-%d %H:%M:%S %Z', ['2010-01-01 12:00:00 UTC'] * 2, @@ -215,12 +215,11 @@ def test_to_datetime_format_weeks(self, cache): pd.Timestamp('2010-01-01 12:00:00', tzinfo=pytz.FixedOffset(0))]]]) def test_to_datetime_parse_tzname_or_tzoffset(self, box, const, - assert_equal, fmt, - dates, expected_dates): + fmt, dates, expected_dates): # GH 13486 result = pd.to_datetime(dates, format=fmt, box=box) expected = const(expected_dates) - getattr(tm, assert_equal)(result, expected) + tm.assert_equal(result, expected) with pytest.raises(ValueError): pd.to_datetime(dates, format=fmt, box=box, utc=True) @@ -1049,17 +1048,16 @@ def test_to_datetime_types(self, cache): # assert result == expected @pytest.mark.parametrize('cache', [True, False]) - @pytest.mark.parametrize('box, klass, assert_method', [ - [True, Index, 'assert_index_equal'], - [False, np.array, 'assert_numpy_array_equal'] + @pytest.mark.parametrize('box, klass', [ + [True, Index], + [False, np.array] ]) - def test_to_datetime_unprocessable_input(self, cache, box, klass, - assert_method): + def test_to_datetime_unprocessable_input(self, cache, box, klass): # GH 4928 # GH 21864 result = to_datetime([1, '1'], errors='ignore', cache=cache, box=box) expected = klass(np.array([1, '1'], dtype='O')) - getattr(tm, assert_method)(result, expected) + tm.assert_equal(result, expected) pytest.raises(TypeError, to_datetime, [1, '1'], errors='raise', cache=cache, box=box) diff --git a/pandas/tests/scalar/test_nat.py b/pandas/tests/scalar/test_nat.py index a6b217a37bd0c8..bc8582d9b7d295 100644 --- a/pandas/tests/scalar/test_nat.py +++ b/pandas/tests/scalar/test_nat.py @@ -312,19 +312,16 @@ def test_nat_arithmetic_index(): tm.assert_index_equal(NaT - tdi, tdi_nat) -@pytest.mark.parametrize('box, assert_func', [ - (TimedeltaIndex, tm.assert_index_equal), - (Series, tm.assert_series_equal) -]) -def test_nat_arithmetic_td64_vector(box, assert_func): +@pytest.mark.parametrize('box', [TimedeltaIndex, Series]) +def test_nat_arithmetic_td64_vector(box): # GH#19124 vec = box(['1 day', '2 day'], dtype='timedelta64[ns]') box_nat = box([NaT, NaT], dtype='timedelta64[ns]') - assert_func(vec + NaT, box_nat) - assert_func(NaT + vec, box_nat) - assert_func(vec - NaT, box_nat) - assert_func(NaT - vec, box_nat) + tm.assert_equal(vec + NaT, box_nat) + tm.assert_equal(NaT + vec, box_nat) + tm.assert_equal(vec - NaT, box_nat) + tm.assert_equal(NaT - vec, box_nat) def test_nat_pinned_docstrings(): diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index b8fabbf52159d7..bda4d71d58e828 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -2516,10 +2516,8 @@ def test_onOffset(self, case): dt, expected = case assert_onOffset(SemiMonthEnd(), dt, expected) - @pytest.mark.parametrize('klass,assert_func', - [(Series, tm.assert_series_equal), - (DatetimeIndex, tm.assert_index_equal)]) - def test_vectorized_offset_addition(self, klass, assert_func): + @pytest.mark.parametrize('klass', [Series, DatetimeIndex]) + def test_vectorized_offset_addition(self, klass): s = klass([Timestamp('2000-01-15 00:15:00', tz='US/Central'), Timestamp('2000-02-15', tz='US/Central')], name='a') @@ -2527,8 +2525,8 @@ def test_vectorized_offset_addition(self, klass, assert_func): result2 = SemiMonthEnd() + s exp = klass([Timestamp('2000-01-31 00:15:00', tz='US/Central'), Timestamp('2000-02-29', tz='US/Central')], name='a') - assert_func(result, exp) - assert_func(result2, exp) + tm.assert_equal(result, exp) + tm.assert_equal(result2, exp) s = klass([Timestamp('2000-01-01 00:15:00', tz='US/Central'), Timestamp('2000-02-01', tz='US/Central')], name='a') @@ -2536,8 +2534,8 @@ def test_vectorized_offset_addition(self, klass, assert_func): result2 = SemiMonthEnd() + s exp = klass([Timestamp('2000-01-15 00:15:00', tz='US/Central'), Timestamp('2000-02-15', tz='US/Central')], name='a') - assert_func(result, exp) - assert_func(result2, exp) + tm.assert_equal(result, exp) + tm.assert_equal(result2, exp) class TestSemiMonthBegin(Base): @@ -2692,18 +2690,16 @@ def test_onOffset(self, case): dt, expected = case assert_onOffset(SemiMonthBegin(), dt, expected) - @pytest.mark.parametrize('klass,assert_func', - [(Series, tm.assert_series_equal), - (DatetimeIndex, tm.assert_index_equal)]) - def test_vectorized_offset_addition(self, klass, assert_func): + @pytest.mark.parametrize('klass', [Series, DatetimeIndex]) + def test_vectorized_offset_addition(self, klass): s = klass([Timestamp('2000-01-15 00:15:00', tz='US/Central'), Timestamp('2000-02-15', tz='US/Central')], name='a') result = s + SemiMonthBegin() result2 = SemiMonthBegin() + s exp = klass([Timestamp('2000-02-01 00:15:00', tz='US/Central'), Timestamp('2000-03-01', tz='US/Central')], name='a') - assert_func(result, exp) - assert_func(result2, exp) + tm.assert_equal(result, exp) + tm.assert_equal(result2, exp) s = klass([Timestamp('2000-01-01 00:15:00', tz='US/Central'), Timestamp('2000-02-01', tz='US/Central')], name='a') @@ -2711,8 +2707,8 @@ def test_vectorized_offset_addition(self, klass, assert_func): result2 = SemiMonthBegin() + s exp = klass([Timestamp('2000-01-15 00:15:00', tz='US/Central'), Timestamp('2000-02-15', tz='US/Central')], name='a') - assert_func(result, exp) - assert_func(result2, exp) + tm.assert_equal(result, exp) + tm.assert_equal(result2, exp) def test_Easter(): diff --git a/pandas/util/testing.py b/pandas/util/testing.py index 3db251e89842d5..4e01e0feb004cc 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -1522,8 +1522,8 @@ def assert_equal(left, right, **kwargs): Parameters ---------- - left : Index, Series, or DataFrame - right : Index, Series, or DataFrame + left : Index, Series, DataFrame, ExtensionArray, or np.ndarray + right : Index, Series, DataFrame, ExtensionArray, or np.ndarray **kwargs """ if isinstance(left, pd.Index): @@ -1532,6 +1532,10 @@ def assert_equal(left, right, **kwargs): assert_series_equal(left, right, **kwargs) elif isinstance(left, pd.DataFrame): assert_frame_equal(left, right, **kwargs) + elif isinstance(left, ExtensionArray): + assert_extension_array_equal(left, right, **kwargs) + elif isinstance(left, np.ndarray): + assert_numpy_array_equal(left, right, **kwargs) else: raise NotImplementedError(type(left)) From a6c1ff15eeb8310cc1992889283e0f49bbc804a1 Mon Sep 17 00:00:00 2001 From: Justin Zheng Date: Thu, 4 Oct 2018 16:13:57 -0700 Subject: [PATCH 80/85] BUG GH22858 When creating empty dataframe, only cast int to float if index given (#22963) --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/core/dtypes/cast.py | 4 +++- pandas/tests/frame/test_constructors.py | 27 ++++++++++--------------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index c9874b4dd03d65..164772eb38fa76 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -199,6 +199,7 @@ Other Enhancements Backwards incompatible API changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +- A newly constructed empty :class:`DataFrame` with integer as the ``dtype`` will now only be cast to ``float64`` if ``index`` is specified (:issue:`22858`) .. _whatsnew_0240.api_breaking.interval_values: diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index 410e061c895db5..a95a45d5f9ae4e 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -1220,7 +1220,9 @@ def construct_1d_arraylike_from_scalar(value, length, dtype): dtype = dtype.dtype # coerce if we have nan for an integer dtype - if is_integer_dtype(dtype) and isna(value): + # GH 22858: only cast to float if an index + # (passed here as length) is specified + if length and is_integer_dtype(dtype) and isna(value): dtype = np.float64 subarr = np.empty(length, dtype=dtype) subarr.fill(value) diff --git a/pandas/tests/frame/test_constructors.py b/pandas/tests/frame/test_constructors.py index 2f1c9e05a01b05..e2be410d51b88a 100644 --- a/pandas/tests/frame/test_constructors.py +++ b/pandas/tests/frame/test_constructors.py @@ -798,25 +798,20 @@ def test_constructor_mrecarray(self): result = DataFrame(mrecs, index=[1, 2]) assert_fr_equal(result, expected) - def test_constructor_corner(self): + def test_constructor_corner_shape(self): df = DataFrame(index=[]) assert df.values.shape == (0, 0) - # empty but with specified dtype - df = DataFrame(index=lrange(10), columns=['a', 'b'], dtype=object) - assert df.values.dtype == np.object_ - - # does not error but ends up float - df = DataFrame(index=lrange(10), columns=['a', 'b'], dtype=int) - assert df.values.dtype == np.dtype('float64') - - # #1783 empty dtype object - df = DataFrame({}, columns=['foo', 'bar']) - assert df.values.dtype == np.object_ - - df = DataFrame({'b': 1}, index=lrange(10), columns=list('abc'), - dtype=int) - assert df.values.dtype == np.dtype('float64') + @pytest.mark.parametrize("data, index, columns, dtype, expected", [ + (None, lrange(10), ['a', 'b'], object, np.object_), + (None, None, ['a', 'b'], 'int64', np.dtype('int64')), + (None, lrange(10), ['a', 'b'], int, np.dtype('float64')), + ({}, None, ['foo', 'bar'], None, np.object_), + ({'b': 1}, lrange(10), list('abc'), int, np.dtype('float64')) + ]) + def test_constructor_dtype(self, data, index, columns, dtype, expected): + df = DataFrame(data, index, columns, dtype) + assert df.values.dtype == expected def test_constructor_scalar_inference(self): data = {'int': 1, 'bool': True, From 4976ee1c93bcc6327ca5e8714ad326c126f4151e Mon Sep 17 00:00:00 2001 From: svenharris <32942101+svenharris@users.noreply.github.com> Date: Fri, 5 Oct 2018 00:29:06 +0100 Subject: [PATCH 81/85] Merge asof float fix (#22982) * allow tolerance in merge_asof with float type * BUG: GH22981 allow tolerance in merge_asof with float type * BUG: GH22981 allow tolerance in merge_asof with float type --- doc/source/whatsnew/v0.24.0.txt | 1 + pandas/core/reshape/merge.py | 9 ++++++++- pandas/tests/reshape/merge/test_merge_asof.py | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 164772eb38fa76..91575c311b409e 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -849,6 +849,7 @@ Reshaping - Bug in :meth:`DataFrame.drop_duplicates` for empty ``DataFrame`` which incorrectly raises an error (:issue:`20516`) - Bug in :func:`pandas.wide_to_long` when a string is passed to the stubnames argument and a column name is a substring of that stubname (:issue:`22468`) - Bug in :func:`merge` when merging ``datetime64[ns, tz]`` data that contained a DST transition (:issue:`18885`) +- Bug in :func:`merge_asof` when merging on float values within defined tolerance (:issue:`22981`) Build Changes ^^^^^^^^^^^^^ diff --git a/pandas/core/reshape/merge.py b/pandas/core/reshape/merge.py index c4305136accb15..d0c7b66978661f 100644 --- a/pandas/core/reshape/merge.py +++ b/pandas/core/reshape/merge.py @@ -23,6 +23,7 @@ is_categorical_dtype, is_integer_dtype, is_float_dtype, + is_number, is_numeric_dtype, is_integer, is_int_or_datetime_dtype, @@ -1356,8 +1357,14 @@ def _get_merge_keys(self): if self.tolerance < 0: raise MergeError("tolerance must be positive") + elif is_float_dtype(lt): + if not is_number(self.tolerance): + raise MergeError(msg) + if self.tolerance < 0: + raise MergeError("tolerance must be positive") + else: - raise MergeError("key must be integer or timestamp") + raise MergeError("key must be integer, timestamp or float") # validate allow_exact_matches if not is_bool(self.allow_exact_matches): diff --git a/pandas/tests/reshape/merge/test_merge_asof.py b/pandas/tests/reshape/merge/test_merge_asof.py index d5df9d3820fdc6..c75a6a707cafc1 100644 --- a/pandas/tests/reshape/merge/test_merge_asof.py +++ b/pandas/tests/reshape/merge/test_merge_asof.py @@ -642,6 +642,21 @@ def test_tolerance_tz(self): 'value2': list("BCDEE")}) 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 = pd.merge_asof(left, right, on='a', direction='nearest', + tolerance=0.5) + assert_frame_equal(result, expected) + def test_index_tolerance(self): # GH 15135 expected = self.tolerance.set_index('time') From 28fd07aec50ef654c5301cf2a6e5ba8e2e22c2a4 Mon Sep 17 00:00:00 2001 From: Vyom Jain Date: Fri, 5 Oct 2018 17:28:40 +0530 Subject: [PATCH 82/85] fix typos (#23003) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f26b9598bb5d37..b4dedecb4c6971 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ 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. +its way towards this goal. ## Main Features Here are just a few of the things that pandas does well: From ee27fabb240f99dfd278e1540c5e4ca430a490f0 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 5 Oct 2018 07:00:38 -0500 Subject: [PATCH 83/85] Use ._tshift internally for datetimelike ops (#22949) --- pandas/core/arrays/datetimelike.py | 23 ++++++++++++++++++++--- pandas/core/arrays/period.py | 9 ++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 91c119808db520..1ce60510c6a696 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -455,7 +455,7 @@ def _sub_period_array(self, other): def _addsub_int_array(self, other, op): """ Add or subtract array-like of integers equivalent to applying - `shift` pointwise. + `_time_shift` pointwise. Parameters ---------- @@ -553,6 +553,23 @@ def shift(self, periods, freq=None): -------- Index.shift : Shift values of Index. """ + return self._time_shift(periods=periods, freq=freq) + + def _time_shift(self, periods, freq=None): + """ + Shift each value by `periods`. + + Note this is different from ExtensionArray.shift, which + shifts the *position* of each element, padding the end with + missing values. + + Parameters + ---------- + periods : int + Number of periods to shift by. + freq : pandas.DateOffset, pandas.Timedelta, or string + Frequency increment to shift by. + """ if freq is not None and freq != self.freq: if isinstance(freq, compat.string_types): freq = frequencies.to_offset(freq) @@ -600,7 +617,7 @@ def __add__(self, other): elif lib.is_integer(other): # This check must come after the check for np.timedelta64 # as is_integer returns True for these - result = self.shift(other) + result = self._time_shift(other) # array-like others elif is_timedelta64_dtype(other): @@ -652,7 +669,7 @@ def __sub__(self, other): elif lib.is_integer(other): # This check must come after the check for np.timedelta64 # as is_integer returns True for these - result = self.shift(-other) + result = self._time_shift(-other) elif isinstance(other, Period): result = self._sub_period(other) diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 41b4c5c669efcc..92803ab5f52e06 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -297,7 +297,7 @@ def _add_offset(self, other): if base != self.freq.rule_code: msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr) raise IncompatibleFrequency(msg) - return self.shift(other.n) + return self._time_shift(other.n) def _add_delta_td(self, other): assert isinstance(other, (timedelta, np.timedelta64, Tick)) @@ -307,7 +307,7 @@ def _add_delta_td(self, other): if isinstance(own_offset, Tick): offset_nanos = delta_to_nanoseconds(own_offset) if np.all(nanos % offset_nanos == 0): - return self.shift(nanos // offset_nanos) + return self._time_shift(nanos // offset_nanos) # raise when input doesn't have freq raise IncompatibleFrequency("Input has different freq from " @@ -317,7 +317,7 @@ def _add_delta_td(self, other): def _add_delta(self, other): ordinal_delta = self._maybe_convert_timedelta(other) - return self.shift(ordinal_delta) + return self._time_shift(ordinal_delta) def shift(self, n): """ @@ -332,6 +332,9 @@ def shift(self, n): ------- shifted : Period Array/Index """ + return self._time_shift(n) + + def _time_shift(self, n): values = self._ndarray_values + n * self.freq.n if self.hasnans: values[self._isnan] = iNaT From d523d9fe056d88c0a80a016123eca0f3ae734ae1 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Fri, 5 Oct 2018 05:18:40 -0700 Subject: [PATCH 84/85] Use fused types for _take_2d (#22917) --- pandas/_libs/algos_common_helper.pxi.in | 1 - pandas/_libs/algos_rank_helper.pxi.in | 9 ---- pandas/_libs/algos_take_helper.pxi.in | 36 +++++++++------ pandas/_libs/join_func_helper.pxi.in | 44 +++++++++--------- pandas/_libs/join_helper.pxi.in | 61 +++++++++++++++---------- 5 files changed, 81 insertions(+), 70 deletions(-) diff --git a/pandas/_libs/algos_common_helper.pxi.in b/pandas/_libs/algos_common_helper.pxi.in index 40b1b1a2826705..9f531f36d1a64b 100644 --- a/pandas/_libs/algos_common_helper.pxi.in +++ b/pandas/_libs/algos_common_helper.pxi.in @@ -2,7 +2,6 @@ Template for each `dtype` helper function using 1-d template # 1-d template -- map_indices - pad - pad_1d - pad_2d diff --git a/pandas/_libs/algos_rank_helper.pxi.in b/pandas/_libs/algos_rank_helper.pxi.in index b2551f37339040..130276ae0e73c0 100644 --- a/pandas/_libs/algos_rank_helper.pxi.in +++ b/pandas/_libs/algos_rank_helper.pxi.in @@ -24,17 +24,8 @@ dtypes = [('object', 'object', 'Infinity()', 'NegInfinity()'), @cython.wraparound(False) @cython.boundscheck(False) -{{if dtype == 'object'}} - - def rank_1d_{{dtype}}(object in_arr, ties_method='average', ascending=True, na_option='keep', pct=False): -{{else}} - - -def rank_1d_{{dtype}}(object in_arr, ties_method='average', ascending=True, - na_option='keep', pct=False): -{{endif}} """ Fast NaN-friendly version of scipy.stats.rankdata """ diff --git a/pandas/_libs/algos_take_helper.pxi.in b/pandas/_libs/algos_take_helper.pxi.in index 0e69324acd3411..358479c837d050 100644 --- a/pandas/_libs/algos_take_helper.pxi.in +++ b/pandas/_libs/algos_take_helper.pxi.in @@ -260,33 +260,39 @@ def take_2d_multi_{{name}}_{{dest}}(ndarray[{{c_type_in}}, ndim=2] values, {{endfor}} -#---------------------------------------------------------------------- +# ---------------------------------------------------------------------- # take_2d internal function -#---------------------------------------------------------------------- +# ---------------------------------------------------------------------- -{{py: - -# dtype, ctype, init_result -dtypes = [('float64', 'float64_t', 'np.empty_like(values)'), - ('uint64', 'uint64_t', 'np.empty_like(values)'), - ('object', 'object', 'values.copy()'), - ('int64', 'int64_t', 'np.empty_like(values)')] -}} +ctypedef fused take_t: + float64_t + uint64_t + int64_t + object -{{for dtype, ctype, init_result in dtypes}} -cdef _take_2d_{{dtype}}(ndarray[{{ctype}}, ndim=2] values, object idx): +cdef _take_2d(ndarray[take_t, ndim=2] values, object idx): cdef: Py_ssize_t i, j, N, K ndarray[Py_ssize_t, ndim=2, cast=True] indexer = idx - ndarray[{{ctype}}, ndim=2] result + ndarray[take_t, ndim=2] result object val N, K = ( values).shape - result = {{init_result}} + + if take_t is object: + # evaluated at compile-time + result = values.copy() + else: + result = np.empty_like(values) + for i in range(N): for j in range(K): result[i, j] = values[i, indexer[i, j]] return result -{{endfor}} + +_take_2d_object = _take_2d[object] +_take_2d_float64 = _take_2d[float64_t] +_take_2d_int64 = _take_2d[int64_t] +_take_2d_uint64 = _take_2d[uint64_t] diff --git a/pandas/_libs/join_func_helper.pxi.in b/pandas/_libs/join_func_helper.pxi.in index 73d231b8588dc3..a72b113a6fdb61 100644 --- a/pandas/_libs/join_func_helper.pxi.in +++ b/pandas/_libs/join_func_helper.pxi.in @@ -68,21 +68,21 @@ def asof_join_backward_{{on_dtype}}_by_{{by_dtype}}( # find last position in right whose value is less than left's if allow_exact_matches: - while right_pos < right_size and\ - right_values[right_pos] <= left_values[left_pos]: + while (right_pos < right_size and + right_values[right_pos] <= left_values[left_pos]): hash_table.set_item(right_by_values[right_pos], right_pos) right_pos += 1 else: - while right_pos < right_size and\ - right_values[right_pos] < left_values[left_pos]: + while (right_pos < right_size and + right_values[right_pos] < left_values[left_pos]): hash_table.set_item(right_by_values[right_pos], right_pos) right_pos += 1 right_pos -= 1 # save positions as the desired index by_value = left_by_values[left_pos] - found_right_pos = hash_table.get_item(by_value)\ - if by_value in hash_table else -1 + found_right_pos = (hash_table.get_item(by_value) + if by_value in hash_table else -1) left_indexer[left_pos] = left_pos right_indexer[left_pos] = found_right_pos @@ -133,21 +133,21 @@ def asof_join_forward_{{on_dtype}}_by_{{by_dtype}}( # find first position in right whose value is greater than left's if allow_exact_matches: - while right_pos >= 0 and\ - right_values[right_pos] >= left_values[left_pos]: + while (right_pos >= 0 and + right_values[right_pos] >= left_values[left_pos]): hash_table.set_item(right_by_values[right_pos], right_pos) right_pos -= 1 else: - while right_pos >= 0 and\ - right_values[right_pos] > left_values[left_pos]: + while (right_pos >= 0 and + right_values[right_pos] > left_values[left_pos]): hash_table.set_item(right_by_values[right_pos], right_pos) right_pos -= 1 right_pos += 1 # save positions as the desired index by_value = left_by_values[left_pos] - found_right_pos = hash_table.get_item(by_value)\ - if by_value in hash_table else -1 + found_right_pos = (hash_table.get_item(by_value) + if by_value in hash_table else -1) left_indexer[left_pos] = left_pos right_indexer[left_pos] = found_right_pos @@ -259,12 +259,12 @@ def asof_join_backward_{{on_dtype}}( # find last position in right whose value is less than left's if allow_exact_matches: - while right_pos < right_size and\ - right_values[right_pos] <= left_values[left_pos]: + while (right_pos < right_size and + right_values[right_pos] <= left_values[left_pos]): right_pos += 1 else: - while right_pos < right_size and\ - right_values[right_pos] < left_values[left_pos]: + while (right_pos < right_size and + right_values[right_pos] < left_values[left_pos]): right_pos += 1 right_pos -= 1 @@ -313,19 +313,19 @@ def asof_join_forward_{{on_dtype}}( # find first position in right whose value is greater than left's if allow_exact_matches: - while right_pos >= 0 and\ - right_values[right_pos] >= left_values[left_pos]: + while (right_pos >= 0 and + right_values[right_pos] >= left_values[left_pos]): right_pos -= 1 else: - while right_pos >= 0 and\ - right_values[right_pos] > left_values[left_pos]: + while (right_pos >= 0 and + right_values[right_pos] > left_values[left_pos]): right_pos -= 1 right_pos += 1 # save positions as the desired index left_indexer[left_pos] = left_pos - right_indexer[left_pos] = right_pos\ - if right_pos != right_size else -1 + right_indexer[left_pos] = (right_pos + if right_pos != right_size else -1) # if needed, verify that tolerance is met if has_tolerance and right_pos != right_size: diff --git a/pandas/_libs/join_helper.pxi.in b/pandas/_libs/join_helper.pxi.in index feb8cfb76a7f0d..6ba587a5b04eac 100644 --- a/pandas/_libs/join_helper.pxi.in +++ b/pandas/_libs/join_helper.pxi.in @@ -4,42 +4,30 @@ Template for each `dtype` helper function for join WARNING: DO NOT edit .pxi FILE directly, .pxi is generated from .pxi.in """ -#---------------------------------------------------------------------- +# ---------------------------------------------------------------------- # left_join_indexer, inner_join_indexer, outer_join_indexer -#---------------------------------------------------------------------- +# ---------------------------------------------------------------------- -{{py: - -# name, c_type, dtype -dtypes = [('float64', 'float64_t', 'np.float64'), - ('float32', 'float32_t', 'np.float32'), - ('object', 'object', 'object'), - ('int32', 'int32_t', 'np.int32'), - ('int64', 'int64_t', 'np.int64'), - ('uint64', 'uint64_t', 'np.uint64')] - -def get_dispatch(dtypes): - - for name, c_type, dtype in dtypes: - yield name, c_type, dtype - -}} +ctypedef fused join_t: + float64_t + float32_t + object + int32_t + int64_t + uint64_t -{{for name, c_type, dtype in get_dispatch(dtypes)}} # Joins on ordered, unique indices # right might contain non-unique values - @cython.wraparound(False) @cython.boundscheck(False) -def left_join_indexer_unique_{{name}}(ndarray[{{c_type}}] left, - ndarray[{{c_type}}] right): +def left_join_indexer_unique(ndarray[join_t] left, ndarray[join_t] right): cdef: Py_ssize_t i, j, nleft, nright ndarray[int64_t] indexer - {{c_type}} lval, rval + join_t lval, rval i = 0 j = 0 @@ -78,6 +66,33 @@ def left_join_indexer_unique_{{name}}(ndarray[{{c_type}}] left, return indexer +left_join_indexer_unique_float64 = left_join_indexer_unique["float64_t"] +left_join_indexer_unique_float32 = left_join_indexer_unique["float32_t"] +left_join_indexer_unique_object = left_join_indexer_unique["object"] +left_join_indexer_unique_int32 = left_join_indexer_unique["int32_t"] +left_join_indexer_unique_int64 = left_join_indexer_unique["int64_t"] +left_join_indexer_unique_uint64 = left_join_indexer_unique["uint64_t"] + + +{{py: + +# name, c_type, dtype +dtypes = [('float64', 'float64_t', 'np.float64'), + ('float32', 'float32_t', 'np.float32'), + ('object', 'object', 'object'), + ('int32', 'int32_t', 'np.int32'), + ('int64', 'int64_t', 'np.int64'), + ('uint64', 'uint64_t', 'np.uint64')] + +def get_dispatch(dtypes): + + for name, c_type, dtype in dtypes: + yield name, c_type, dtype + +}} + +{{for name, c_type, dtype in get_dispatch(dtypes)}} + # @cython.wraparound(False) # @cython.boundscheck(False) def left_join_indexer_{{name}}(ndarray[{{c_type}}] left, From 5551bcf9d297ea8a0aeffb70b17ae6730e8abf89 Mon Sep 17 00:00:00 2001 From: h-vetinari <33685575+h-vetinari@users.noreply.github.com> Date: Sat, 6 Oct 2018 17:50:19 +0200 Subject: [PATCH 85/85] TST/CLN: Fixturize frame/test_analytics (#22733) --- pandas/tests/frame/conftest.py | 29 ++ pandas/tests/frame/test_analytics.py | 719 ++++++++++++++------------- 2 files changed, 390 insertions(+), 358 deletions(-) diff --git a/pandas/tests/frame/conftest.py b/pandas/tests/frame/conftest.py index 4a4ce4540b9d5c..348331fc0ccdff 100644 --- a/pandas/tests/frame/conftest.py +++ b/pandas/tests/frame/conftest.py @@ -17,6 +17,20 @@ def float_frame(): return DataFrame(tm.getSeriesData()) +@pytest.fixture +def float_frame_with_na(): + """ + Fixture for DataFrame of floats with index of unique strings + + Columns are ['A', 'B', 'C', 'D']; some entries are missing + """ + df = DataFrame(tm.getSeriesData()) + # set some NAs + df.loc[5:10] = np.nan + df.loc[15:20, -2:] = np.nan + return df + + @pytest.fixture def float_frame2(): """ @@ -27,6 +41,21 @@ def float_frame2(): return DataFrame(tm.getSeriesData(), columns=['D', 'C', 'B', 'A']) +@pytest.fixture +def bool_frame_with_na(): + """ + Fixture for DataFrame of booleans with index of unique strings + + Columns are ['A', 'B', 'C', 'D']; some entries are missing + """ + df = DataFrame(tm.getSeriesData()) > 0 + df = df.astype(object) + # set some NAs + df.loc[5:10] = np.nan + df.loc[15:20, -2:] = np.nan + return df + + @pytest.fixture def int_frame(): """ diff --git a/pandas/tests/frame/test_analytics.py b/pandas/tests/frame/test_analytics.py index baebf414969bea..b0b9f2815cbb97 100644 --- a/pandas/tests/frame/test_analytics.py +++ b/pandas/tests/frame/test_analytics.py @@ -23,54 +23,188 @@ import pandas.util.testing as tm import pandas.util._test_decorators as td -from pandas.tests.frame.common import TestData -class TestDataFrameAnalytics(TestData): +def _check_stat_op(name, alternative, main_frame, float_frame, + float_string_frame, has_skipna=True, + has_numeric_only=False, check_dtype=True, + check_dates=False, check_less_precise=False, + skipna_alternative=None): + + f = getattr(main_frame, name) + + if check_dates: + df = DataFrame({'b': date_range('1/1/2001', periods=2)}) + _f = getattr(df, name) + result = _f() + assert isinstance(result, Series) + + df['a'] = lrange(len(df)) + result = getattr(df, name)() + assert isinstance(result, Series) + assert len(result) + + if has_skipna: + def wrapper(x): + return alternative(x.values) + + skipna_wrapper = tm._make_skipna_wrapper(alternative, + skipna_alternative) + result0 = f(axis=0, skipna=False) + result1 = f(axis=1, skipna=False) + tm.assert_series_equal(result0, main_frame.apply(wrapper), + check_dtype=check_dtype, + check_less_precise=check_less_precise) + # HACK: win32 + tm.assert_series_equal(result1, main_frame.apply(wrapper, axis=1), + check_dtype=False, + check_less_precise=check_less_precise) + else: + skipna_wrapper = alternative + + result0 = f(axis=0) + result1 = f(axis=1) + tm.assert_series_equal(result0, main_frame.apply(skipna_wrapper), + check_dtype=check_dtype, + check_less_precise=check_less_precise) + if name in ['sum', 'prod']: + expected = main_frame.apply(skipna_wrapper, axis=1) + tm.assert_series_equal(result1, expected, check_dtype=False, + check_less_precise=check_less_precise) + + # check dtypes + if check_dtype: + lcd_dtype = main_frame.values.dtype + assert lcd_dtype == result0.dtype + assert lcd_dtype == result1.dtype + + # bad axis + tm.assert_raises_regex(ValueError, 'No axis named 2', f, axis=2) + # make sure works on mixed-type frame + getattr(float_string_frame, name)(axis=0) + getattr(float_string_frame, name)(axis=1) + + if has_numeric_only: + getattr(float_string_frame, name)(axis=0, numeric_only=True) + getattr(float_string_frame, name)(axis=1, numeric_only=True) + getattr(float_frame, name)(axis=0, numeric_only=False) + getattr(float_frame, name)(axis=1, numeric_only=False) + + # all NA case + if has_skipna: + all_na = float_frame * np.NaN + r0 = getattr(all_na, name)(axis=0) + r1 = getattr(all_na, name)(axis=1) + if name in ['sum', 'prod']: + unit = int(name == 'prod') + expected = pd.Series(unit, index=r0.index, dtype=r0.dtype) + tm.assert_series_equal(r0, expected) + expected = pd.Series(unit, index=r1.index, dtype=r1.dtype) + tm.assert_series_equal(r1, expected) + + +def _check_bool_op(name, alternative, frame, float_string_frame, + has_skipna=True, has_bool_only=False): + + f = getattr(frame, name) + + if has_skipna: + def skipna_wrapper(x): + nona = x.dropna().values + return alternative(nona) + + def wrapper(x): + return alternative(x.values) + + result0 = f(axis=0, skipna=False) + result1 = f(axis=1, skipna=False) + tm.assert_series_equal(result0, frame.apply(wrapper)) + tm.assert_series_equal(result1, frame.apply(wrapper, axis=1), + check_dtype=False) # HACK: win32 + else: + skipna_wrapper = alternative + wrapper = alternative + + result0 = f(axis=0) + result1 = f(axis=1) + tm.assert_series_equal(result0, frame.apply(skipna_wrapper)) + tm.assert_series_equal(result1, frame.apply(skipna_wrapper, axis=1), + check_dtype=False) + + # bad axis + pytest.raises(ValueError, f, axis=2) + + # make sure works on mixed-type frame + mixed = float_string_frame + mixed['_bool_'] = np.random.randn(len(mixed)) > 0 + getattr(mixed, name)(axis=0) + getattr(mixed, name)(axis=1) + + class NonzeroFail(object): + + def __nonzero__(self): + raise ValueError + + mixed['_nonzero_fail_'] = NonzeroFail() + + if has_bool_only: + getattr(mixed, name)(axis=0, bool_only=True) + getattr(mixed, name)(axis=1, bool_only=True) + getattr(frame, name)(axis=0, bool_only=False) + getattr(frame, name)(axis=1, bool_only=False) + + # all NA case + if has_skipna: + all_na = frame * np.NaN + r0 = getattr(all_na, name)(axis=0) + r1 = getattr(all_na, name)(axis=1) + if name == 'any': + assert not r0.any() + assert not r1.any() + else: + assert r0.all() + assert r1.all() + + +class TestDataFrameAnalytics(): # ---------------------------------------------------------------------= # Correlation and covariance @td.skip_if_no_scipy - def test_corr_pearson(self): - self.frame['A'][:5] = nan - self.frame['B'][5:10] = nan + def test_corr_pearson(self, float_frame): + float_frame['A'][:5] = nan + float_frame['B'][5:10] = nan - self._check_method('pearson') + self._check_method(float_frame, 'pearson') @td.skip_if_no_scipy - def test_corr_kendall(self): - self.frame['A'][:5] = nan - self.frame['B'][5:10] = nan + def test_corr_kendall(self, float_frame): + float_frame['A'][:5] = nan + float_frame['B'][5:10] = nan - self._check_method('kendall') + self._check_method(float_frame, 'kendall') @td.skip_if_no_scipy - def test_corr_spearman(self): - self.frame['A'][:5] = nan - self.frame['B'][5:10] = nan + def test_corr_spearman(self, float_frame): + float_frame['A'][:5] = nan + float_frame['B'][5:10] = nan - self._check_method('spearman') + self._check_method(float_frame, 'spearman') - def _check_method(self, method='pearson', check_minp=False): - if not check_minp: - correls = self.frame.corr(method=method) - exp = self.frame['A'].corr(self.frame['C'], method=method) - tm.assert_almost_equal(correls['A']['C'], exp) - else: - result = self.frame.corr(min_periods=len(self.frame) - 8) - expected = self.frame.corr() - expected.loc['A', 'B'] = expected.loc['B', 'A'] = nan - tm.assert_frame_equal(result, expected) + def _check_method(self, frame, method='pearson'): + correls = frame.corr(method=method) + expected = frame['A'].corr(frame['C'], method=method) + tm.assert_almost_equal(correls['A']['C'], expected) @td.skip_if_no_scipy - def test_corr_non_numeric(self): - self.frame['A'][:5] = nan - self.frame['B'][5:10] = nan + def test_corr_non_numeric(self, float_frame, float_string_frame): + float_frame['A'][:5] = nan + float_frame['B'][5:10] = nan # exclude non-numeric types - result = self.mixed_frame.corr() - expected = self.mixed_frame.loc[:, ['A', 'B', 'C', 'D']].corr() + result = float_string_frame.corr() + expected = float_string_frame.loc[:, ['A', 'B', 'C', 'D']].corr() tm.assert_frame_equal(result, expected) @td.skip_if_no_scipy @@ -138,36 +272,36 @@ def test_corr_invalid_method(self): with tm.assert_raises_regex(ValueError, msg): df.corr(method="____") - def test_cov(self): + def test_cov(self, float_frame, float_string_frame): # min_periods no NAs (corner case) - expected = self.frame.cov() - result = self.frame.cov(min_periods=len(self.frame)) + expected = float_frame.cov() + result = float_frame.cov(min_periods=len(float_frame)) tm.assert_frame_equal(expected, result) - result = self.frame.cov(min_periods=len(self.frame) + 1) + result = float_frame.cov(min_periods=len(float_frame) + 1) assert isna(result.values).all() # with NAs - frame = self.frame.copy() + frame = float_frame.copy() frame['A'][:5] = nan frame['B'][5:10] = nan - result = self.frame.cov(min_periods=len(self.frame) - 8) - expected = self.frame.cov() + result = float_frame.cov(min_periods=len(float_frame) - 8) + expected = float_frame.cov() expected.loc['A', 'B'] = np.nan expected.loc['B', 'A'] = np.nan # regular - self.frame['A'][:5] = nan - self.frame['B'][:10] = nan - cov = self.frame.cov() + float_frame['A'][:5] = nan + float_frame['B'][:10] = nan + cov = float_frame.cov() tm.assert_almost_equal(cov['A']['C'], - self.frame['A'].cov(self.frame['C'])) + float_frame['A'].cov(float_frame['C'])) # exclude non-numeric types - result = self.mixed_frame.cov() - expected = self.mixed_frame.loc[:, ['A', 'B', 'C', 'D']].cov() + result = float_string_frame.cov() + expected = float_string_frame.loc[:, ['A', 'B', 'C', 'D']].cov() tm.assert_frame_equal(result, expected) # Single column frame @@ -182,11 +316,11 @@ def test_cov(self): index=df.columns, columns=df.columns) tm.assert_frame_equal(result, expected) - def test_corrwith(self): - a = self.tsframe + def test_corrwith(self, datetime_frame): + a = datetime_frame noise = Series(randn(len(a)), index=a.index) - b = self.tsframe.add(noise, axis=0) + b = datetime_frame.add(noise, axis=0) # make sure order does not matter b = b.reindex(columns=b.columns[::-1], index=b.index[::-1][10:]) @@ -231,9 +365,9 @@ def test_corrwith_with_objects(self): expected = df1.loc[:, cols].corrwith(df2.loc[:, cols], axis=1) tm.assert_series_equal(result, expected) - def test_corrwith_series(self): - result = self.tsframe.corrwith(self.tsframe['A']) - expected = self.tsframe.apply(self.tsframe['A'].corr) + def test_corrwith_series(self, datetime_frame): + result = datetime_frame.corrwith(datetime_frame['A']) + expected = datetime_frame.apply(datetime_frame['A'].corr) tm.assert_series_equal(result, expected) @@ -460,13 +594,12 @@ def test_reduce_mixed_frame(self): np.array([2, 150, 'abcde'], dtype=object)) tm.assert_series_equal(test, df.T.sum(axis=1)) - def test_count(self): + def test_count(self, float_frame_with_na, float_frame, float_string_frame): f = lambda s: notna(s).sum() - self._check_stat_op('count', f, - has_skipna=False, - has_numeric_only=True, - check_dtype=False, - check_dates=True) + _check_stat_op('count', f, float_frame_with_na, float_frame, + float_string_frame, has_skipna=False, + has_numeric_only=True, check_dtype=False, + check_dates=True) # corner case frame = DataFrame() @@ -492,10 +625,12 @@ def test_count(self): expected = Series(0, index=[]) tm.assert_series_equal(result, expected) - def test_nunique(self): + def test_nunique(self, float_frame_with_na, float_frame, + float_string_frame): f = lambda s: len(algorithms.unique1d(s.dropna())) - self._check_stat_op('nunique', f, has_skipna=False, - check_dtype=False, check_dates=True) + _check_stat_op('nunique', f, float_frame_with_na, + float_frame, float_string_frame, has_skipna=False, + check_dtype=False, check_dates=True) df = DataFrame({'A': [1, 1, 1], 'B': [1, 2, 3], @@ -507,19 +642,20 @@ def test_nunique(self): tm.assert_series_equal(df.nunique(axis=1, dropna=False), Series({0: 1, 1: 3, 2: 2})) - def test_sum(self): - self._check_stat_op('sum', np.sum, has_numeric_only=True, - skipna_alternative=np.nansum) + def test_sum(self, float_frame_with_na, mixed_float_frame, + float_frame, float_string_frame): + _check_stat_op('sum', np.sum, float_frame_with_na, float_frame, + float_string_frame, has_numeric_only=True, + skipna_alternative=np.nansum) # mixed types (with upcasting happening) - self._check_stat_op('sum', np.sum, - frame=self.mixed_float.astype('float32'), - has_numeric_only=True, check_dtype=False, - check_less_precise=True) + _check_stat_op('sum', np.sum, + mixed_float_frame.astype('float32'), float_frame, + float_string_frame, has_numeric_only=True, + check_dtype=False, check_less_precise=True) - @pytest.mark.parametrize( - "method", ['sum', 'mean', 'prod', 'var', - 'std', 'skew', 'min', 'max']) + @pytest.mark.parametrize('method', ['sum', 'mean', 'prod', 'var', + 'std', 'skew', 'min', 'max']) def test_stat_operators_attempt_obj_array(self, method): # GH #676 data = { @@ -529,8 +665,7 @@ def test_stat_operators_attempt_obj_array(self, method): 'c': [0.00031111847529610595, 0.0014902627951905339, -0.00094099200035979691] } - df1 = DataFrame(data, index=['foo', 'bar', 'baz'], - dtype='O') + df1 = DataFrame(data, index=['foo', 'bar', 'baz'], dtype='O') df2 = DataFrame({0: [np.nan, 2], 1: [np.nan, 3], 2: [np.nan, 4]}, dtype=object) @@ -543,41 +678,50 @@ def test_stat_operators_attempt_obj_array(self, method): if method in ['sum', 'prod']: tm.assert_series_equal(result, expected) - def test_mean(self): - self._check_stat_op('mean', np.mean, check_dates=True) + def test_mean(self, float_frame_with_na, float_frame, float_string_frame): + _check_stat_op('mean', np.mean, float_frame_with_na, + float_frame, float_string_frame, check_dates=True) - def test_product(self): - self._check_stat_op('product', np.prod) + def test_product(self, float_frame_with_na, float_frame, + float_string_frame): + _check_stat_op('product', np.prod, float_frame_with_na, + float_frame, float_string_frame) # TODO: Ensure warning isn't emitted in the first place @pytest.mark.filterwarnings("ignore:All-NaN:RuntimeWarning") - def test_median(self): + def test_median(self, float_frame_with_na, float_frame, + float_string_frame): def wrapper(x): if isna(x).any(): return np.nan return np.median(x) - self._check_stat_op('median', wrapper, check_dates=True) + _check_stat_op('median', wrapper, float_frame_with_na, + float_frame, float_string_frame, check_dates=True) - def test_min(self): + def test_min(self, float_frame_with_na, int_frame, + float_frame, float_string_frame): with warnings.catch_warnings(record=True): warnings.simplefilter("ignore", RuntimeWarning) - self._check_stat_op('min', np.min, check_dates=True) - self._check_stat_op('min', np.min, frame=self.intframe) + _check_stat_op('min', np.min, float_frame_with_na, + float_frame, float_string_frame, + check_dates=True) + _check_stat_op('min', np.min, int_frame, float_frame, + float_string_frame) - def test_cummin(self): - self.tsframe.loc[5:10, 0] = nan - self.tsframe.loc[10:15, 1] = nan - self.tsframe.loc[15:, 2] = nan + def test_cummin(self, datetime_frame): + datetime_frame.loc[5:10, 0] = nan + datetime_frame.loc[10:15, 1] = nan + datetime_frame.loc[15:, 2] = nan # axis = 0 - cummin = self.tsframe.cummin() - expected = self.tsframe.apply(Series.cummin) + cummin = datetime_frame.cummin() + expected = datetime_frame.apply(Series.cummin) tm.assert_frame_equal(cummin, expected) # axis = 1 - cummin = self.tsframe.cummin(axis=1) - expected = self.tsframe.apply(Series.cummin, axis=1) + cummin = datetime_frame.cummin(axis=1) + expected = datetime_frame.apply(Series.cummin, axis=1) tm.assert_frame_equal(cummin, expected) # it works @@ -585,22 +729,22 @@ def test_cummin(self): result = df.cummin() # noqa # fix issue - cummin_xs = self.tsframe.cummin(axis=1) - assert np.shape(cummin_xs) == np.shape(self.tsframe) + cummin_xs = datetime_frame.cummin(axis=1) + assert np.shape(cummin_xs) == np.shape(datetime_frame) - def test_cummax(self): - self.tsframe.loc[5:10, 0] = nan - self.tsframe.loc[10:15, 1] = nan - self.tsframe.loc[15:, 2] = nan + def test_cummax(self, datetime_frame): + datetime_frame.loc[5:10, 0] = nan + datetime_frame.loc[10:15, 1] = nan + datetime_frame.loc[15:, 2] = nan # axis = 0 - cummax = self.tsframe.cummax() - expected = self.tsframe.apply(Series.cummax) + cummax = datetime_frame.cummax() + expected = datetime_frame.apply(Series.cummax) tm.assert_frame_equal(cummax, expected) # axis = 1 - cummax = self.tsframe.cummax(axis=1) - expected = self.tsframe.apply(Series.cummax, axis=1) + cummax = datetime_frame.cummax(axis=1) + expected = datetime_frame.apply(Series.cummax, axis=1) tm.assert_frame_equal(cummax, expected) # it works @@ -608,32 +752,40 @@ def test_cummax(self): result = df.cummax() # noqa # fix issue - cummax_xs = self.tsframe.cummax(axis=1) - assert np.shape(cummax_xs) == np.shape(self.tsframe) + cummax_xs = datetime_frame.cummax(axis=1) + assert np.shape(cummax_xs) == np.shape(datetime_frame) - def test_max(self): + def test_max(self, float_frame_with_na, int_frame, + float_frame, float_string_frame): with warnings.catch_warnings(record=True): warnings.simplefilter("ignore", RuntimeWarning) - self._check_stat_op('max', np.max, check_dates=True) - self._check_stat_op('max', np.max, frame=self.intframe) + _check_stat_op('max', np.max, float_frame_with_na, + float_frame, float_string_frame, + check_dates=True) + _check_stat_op('max', np.max, int_frame, float_frame, + float_string_frame) - def test_mad(self): + def test_mad(self, float_frame_with_na, float_frame, float_string_frame): f = lambda x: np.abs(x - x.mean()).mean() - self._check_stat_op('mad', f) + _check_stat_op('mad', f, float_frame_with_na, float_frame, + float_string_frame) - def test_var_std(self): + def test_var_std(self, float_frame_with_na, datetime_frame, float_frame, + float_string_frame): alt = lambda x: np.var(x, ddof=1) - self._check_stat_op('var', alt) + _check_stat_op('var', alt, float_frame_with_na, float_frame, + float_string_frame) alt = lambda x: np.std(x, ddof=1) - self._check_stat_op('std', alt) + _check_stat_op('std', alt, float_frame_with_na, float_frame, + float_string_frame) - result = self.tsframe.std(ddof=4) - expected = self.tsframe.apply(lambda x: x.std(ddof=4)) + result = datetime_frame.std(ddof=4) + expected = datetime_frame.apply(lambda x: x.std(ddof=4)) tm.assert_almost_equal(result, expected) - result = self.tsframe.var(ddof=4) - expected = self.tsframe.apply(lambda x: x.var(ddof=4)) + result = datetime_frame.var(ddof=4) + expected = datetime_frame.apply(lambda x: x.var(ddof=4)) tm.assert_almost_equal(result, expected) arr = np.repeat(np.random.random((1, 1000)), 1000, 0) @@ -685,19 +837,19 @@ def test_mixed_ops(self, op): result = getattr(df, op)() assert len(result) == 2 - def test_cumsum(self): - self.tsframe.loc[5:10, 0] = nan - self.tsframe.loc[10:15, 1] = nan - self.tsframe.loc[15:, 2] = nan + def test_cumsum(self, datetime_frame): + datetime_frame.loc[5:10, 0] = nan + datetime_frame.loc[10:15, 1] = nan + datetime_frame.loc[15:, 2] = nan # axis = 0 - cumsum = self.tsframe.cumsum() - expected = self.tsframe.apply(Series.cumsum) + cumsum = datetime_frame.cumsum() + expected = datetime_frame.apply(Series.cumsum) tm.assert_frame_equal(cumsum, expected) # axis = 1 - cumsum = self.tsframe.cumsum(axis=1) - expected = self.tsframe.apply(Series.cumsum, axis=1) + cumsum = datetime_frame.cumsum(axis=1) + expected = datetime_frame.apply(Series.cumsum, axis=1) tm.assert_frame_equal(cumsum, expected) # works @@ -705,44 +857,46 @@ def test_cumsum(self): result = df.cumsum() # noqa # fix issue - cumsum_xs = self.tsframe.cumsum(axis=1) - assert np.shape(cumsum_xs) == np.shape(self.tsframe) + cumsum_xs = datetime_frame.cumsum(axis=1) + assert np.shape(cumsum_xs) == np.shape(datetime_frame) - def test_cumprod(self): - self.tsframe.loc[5:10, 0] = nan - self.tsframe.loc[10:15, 1] = nan - self.tsframe.loc[15:, 2] = nan + def test_cumprod(self, datetime_frame): + datetime_frame.loc[5:10, 0] = nan + datetime_frame.loc[10:15, 1] = nan + datetime_frame.loc[15:, 2] = nan # axis = 0 - cumprod = self.tsframe.cumprod() - expected = self.tsframe.apply(Series.cumprod) + cumprod = datetime_frame.cumprod() + expected = datetime_frame.apply(Series.cumprod) tm.assert_frame_equal(cumprod, expected) # axis = 1 - cumprod = self.tsframe.cumprod(axis=1) - expected = self.tsframe.apply(Series.cumprod, axis=1) + cumprod = datetime_frame.cumprod(axis=1) + expected = datetime_frame.apply(Series.cumprod, axis=1) tm.assert_frame_equal(cumprod, expected) # fix issue - cumprod_xs = self.tsframe.cumprod(axis=1) - assert np.shape(cumprod_xs) == np.shape(self.tsframe) + cumprod_xs = datetime_frame.cumprod(axis=1) + assert np.shape(cumprod_xs) == np.shape(datetime_frame) # ints - df = self.tsframe.fillna(0).astype(int) + df = datetime_frame.fillna(0).astype(int) df.cumprod(0) df.cumprod(1) # ints32 - df = self.tsframe.fillna(0).astype(np.int32) + df = datetime_frame.fillna(0).astype(np.int32) df.cumprod(0) df.cumprod(1) - def test_sem(self): + def test_sem(self, float_frame_with_na, datetime_frame, + float_frame, float_string_frame): alt = lambda x: np.std(x, ddof=1) / np.sqrt(len(x)) - self._check_stat_op('sem', alt) + _check_stat_op('sem', alt, float_frame_with_na, + float_frame, float_string_frame) - result = self.tsframe.sem(ddof=4) - expected = self.tsframe.apply( + result = datetime_frame.sem(ddof=4) + expected = datetime_frame.apply( lambda x: x.std(ddof=4) / np.sqrt(len(x))) tm.assert_almost_equal(result, expected) @@ -755,7 +909,7 @@ def test_sem(self): assert not (result < 0).any() @td.skip_if_no_scipy - def test_skew(self): + def test_skew(self, float_frame_with_na, float_frame, float_string_frame): from scipy.stats import skew def alt(x): @@ -763,10 +917,11 @@ def alt(x): return np.nan return skew(x, bias=False) - self._check_stat_op('skew', alt) + _check_stat_op('skew', alt, float_frame_with_na, + float_frame, float_string_frame) @td.skip_if_no_scipy - def test_kurt(self): + def test_kurt(self, float_frame_with_na, float_frame, float_string_frame): from scipy.stats import kurtosis def alt(x): @@ -774,7 +929,8 @@ def alt(x): return np.nan return kurtosis(x, bias=False) - self._check_stat_op('kurt', alt) + _check_stat_op('kurt', alt, float_frame_with_na, + float_frame, float_string_frame) index = MultiIndex(levels=[['bar'], ['one', 'two', 'three'], [0, 1]], labels=[[0, 0, 0, 0, 0, 0], @@ -788,92 +944,6 @@ def alt(x): assert kurt.name is None assert kurt2.name == 'bar' - def _check_stat_op(self, name, alternative, frame=None, has_skipna=True, - has_numeric_only=False, check_dtype=True, - check_dates=False, check_less_precise=False, - skipna_alternative=None): - if frame is None: - frame = self.frame - # set some NAs - frame.loc[5:10] = np.nan - frame.loc[15:20, -2:] = np.nan - - f = getattr(frame, name) - - if check_dates: - df = DataFrame({'b': date_range('1/1/2001', periods=2)}) - _f = getattr(df, name) - result = _f() - assert isinstance(result, Series) - - df['a'] = lrange(len(df)) - result = getattr(df, name)() - assert isinstance(result, Series) - assert len(result) - - if has_skipna: - def wrapper(x): - return alternative(x.values) - - skipna_wrapper = tm._make_skipna_wrapper(alternative, - skipna_alternative) - result0 = f(axis=0, skipna=False) - result1 = f(axis=1, skipna=False) - tm.assert_series_equal(result0, frame.apply(wrapper), - check_dtype=check_dtype, - check_less_precise=check_less_precise) - # HACK: win32 - tm.assert_series_equal(result1, frame.apply(wrapper, axis=1), - check_dtype=False, - check_less_precise=check_less_precise) - else: - skipna_wrapper = alternative - wrapper = alternative - - result0 = f(axis=0) - result1 = f(axis=1) - tm.assert_series_equal(result0, frame.apply(skipna_wrapper), - check_dtype=check_dtype, - check_less_precise=check_less_precise) - if name in ['sum', 'prod']: - exp = frame.apply(skipna_wrapper, axis=1) - tm.assert_series_equal(result1, exp, check_dtype=False, - check_less_precise=check_less_precise) - - # check dtypes - if check_dtype: - lcd_dtype = frame.values.dtype - assert lcd_dtype == result0.dtype - assert lcd_dtype == result1.dtype - - # result = f(axis=1) - # comp = frame.apply(alternative, axis=1).reindex(result.index) - # assert_series_equal(result, comp) - - # bad axis - tm.assert_raises_regex(ValueError, 'No axis named 2', f, axis=2) - # make sure works on mixed-type frame - getattr(self.mixed_frame, name)(axis=0) - getattr(self.mixed_frame, name)(axis=1) - - if has_numeric_only: - getattr(self.mixed_frame, name)(axis=0, numeric_only=True) - getattr(self.mixed_frame, name)(axis=1, numeric_only=True) - getattr(self.frame, name)(axis=0, numeric_only=False) - getattr(self.frame, name)(axis=1, numeric_only=False) - - # all NA case - if has_skipna: - all_na = self.frame * np.NaN - r0 = getattr(all_na, name)(axis=0) - r1 = getattr(all_na, name)(axis=1) - if name in ['sum', 'prod']: - unit = int(name == 'prod') - expected = pd.Series(unit, index=r0.index, dtype=r0.dtype) - tm.assert_series_equal(r0, expected) - expected = pd.Series(unit, index=r1.index, dtype=r1.dtype) - tm.assert_series_equal(r1, expected) - @pytest.mark.parametrize("dropna, expected", [ (True, {'A': [12], 'B': [10.0], @@ -1022,9 +1092,9 @@ def test_operators_timedelta64(self): assert df['off1'].dtype == 'timedelta64[ns]' assert df['off2'].dtype == 'timedelta64[ns]' - def test_sum_corner(self): - axis0 = self.empty.sum(0) - axis1 = self.empty.sum(1) + def test_sum_corner(self, empty_frame): + axis0 = empty_frame.sum(0) + axis1 = empty_frame.sum(1) assert isinstance(axis0, Series) assert isinstance(axis1, Series) assert len(axis0) == 0 @@ -1090,59 +1160,60 @@ def test_sum_nanops_timedelta(self): expected = pd.Series([0, 0, np.nan], dtype='m8[ns]', index=idx) tm.assert_series_equal(result, expected) - def test_sum_object(self): - values = self.frame.values.astype(int) - frame = DataFrame(values, index=self.frame.index, - columns=self.frame.columns) + def test_sum_object(self, float_frame): + values = float_frame.values.astype(int) + frame = DataFrame(values, index=float_frame.index, + columns=float_frame.columns) deltas = frame * timedelta(1) deltas.sum() - def test_sum_bool(self): + def test_sum_bool(self, float_frame): # ensure this works, bug report - bools = np.isnan(self.frame) + bools = np.isnan(float_frame) bools.sum(1) bools.sum(0) - def test_mean_corner(self): + def test_mean_corner(self, float_frame, float_string_frame): # unit test when have object data - the_mean = self.mixed_frame.mean(axis=0) - the_sum = self.mixed_frame.sum(axis=0, numeric_only=True) + the_mean = float_string_frame.mean(axis=0) + the_sum = float_string_frame.sum(axis=0, numeric_only=True) tm.assert_index_equal(the_sum.index, the_mean.index) - assert len(the_mean.index) < len(self.mixed_frame.columns) + assert len(the_mean.index) < len(float_string_frame.columns) # xs sum mixed type, just want to know it works... - the_mean = self.mixed_frame.mean(axis=1) - the_sum = self.mixed_frame.sum(axis=1, numeric_only=True) + the_mean = float_string_frame.mean(axis=1) + the_sum = float_string_frame.sum(axis=1, numeric_only=True) tm.assert_index_equal(the_sum.index, the_mean.index) # take mean of boolean column - self.frame['bool'] = self.frame['A'] > 0 - means = self.frame.mean(0) - assert means['bool'] == self.frame['bool'].values.mean() + float_frame['bool'] = float_frame['A'] > 0 + means = float_frame.mean(0) + assert means['bool'] == float_frame['bool'].values.mean() - def test_stats_mixed_type(self): + def test_stats_mixed_type(self, float_string_frame): # don't blow up - self.mixed_frame.std(1) - self.mixed_frame.var(1) - self.mixed_frame.mean(1) - self.mixed_frame.skew(1) + float_string_frame.std(1) + float_string_frame.var(1) + float_string_frame.mean(1) + float_string_frame.skew(1) # TODO: Ensure warning isn't emitted in the first place @pytest.mark.filterwarnings("ignore:All-NaN:RuntimeWarning") - def test_median_corner(self): + def test_median_corner(self, int_frame, float_frame, float_string_frame): def wrapper(x): if isna(x).any(): return np.nan return np.median(x) - self._check_stat_op('median', wrapper, frame=self.intframe, - check_dtype=False, check_dates=True) + _check_stat_op('median', wrapper, int_frame, float_frame, + float_string_frame, check_dtype=False, + check_dates=True) # Miscellanea - def test_count_objects(self): - dm = DataFrame(self.mixed_frame._series) - df = DataFrame(self.mixed_frame._series) + def test_count_objects(self, float_string_frame): + dm = DataFrame(float_string_frame._series) + df = DataFrame(float_string_frame._series) tm.assert_series_equal(dm.count(), df.count()) tm.assert_series_equal(dm.count(1), df.count(1)) @@ -1160,13 +1231,13 @@ def test_sum_bools(self): # Index of max / min - def test_idxmin(self): - frame = self.frame + def test_idxmin(self, float_frame, int_frame): + frame = float_frame frame.loc[5:10] = np.nan frame.loc[15:20, -2:] = np.nan for skipna in [True, False]: for axis in [0, 1]: - for df in [frame, self.intframe]: + for df in [frame, int_frame]: result = df.idxmin(axis=axis, skipna=skipna) expected = df.apply(Series.idxmin, axis=axis, skipna=skipna) @@ -1174,13 +1245,13 @@ def test_idxmin(self): pytest.raises(ValueError, frame.idxmin, axis=2) - def test_idxmax(self): - frame = self.frame + def test_idxmax(self, float_frame, int_frame): + frame = float_frame frame.loc[5:10] = np.nan frame.loc[15:20, -2:] = np.nan for skipna in [True, False]: for axis in [0, 1]: - for df in [frame, self.intframe]: + for df in [frame, int_frame]: result = df.idxmax(axis=axis, skipna=skipna) expected = df.apply(Series.idxmax, axis=axis, skipna=skipna) @@ -1191,9 +1262,13 @@ def test_idxmax(self): # ---------------------------------------------------------------------- # Logical reductions - def test_any_all(self): - self._check_bool_op('any', np.any, has_skipna=True, has_bool_only=True) - self._check_bool_op('all', np.all, has_skipna=True, has_bool_only=True) + def test_any_all(self, bool_frame_with_na, float_string_frame): + _check_bool_op('any', np.any, bool_frame_with_na, + float_string_frame, has_skipna=True, + has_bool_only=True) + _check_bool_op('all', np.all, bool_frame_with_na, + float_string_frame, has_skipna=True, + has_bool_only=True) def test_any_all_extra(self): df = DataFrame({ @@ -1325,79 +1400,6 @@ def test_any_all_level_axis_none_raises(self, method): with tm.assert_raises_regex(ValueError, xpr): getattr(df, method)(axis=None, level='out') - def _check_bool_op(self, name, alternative, frame=None, has_skipna=True, - has_bool_only=False): - if frame is None: - frame = self.frame > 0 - # set some NAs - frame = DataFrame(frame.values.astype(object), frame.index, - frame.columns) - frame.loc[5:10] = np.nan - frame.loc[15:20, -2:] = np.nan - - f = getattr(frame, name) - - if has_skipna: - def skipna_wrapper(x): - nona = x.dropna().values - return alternative(nona) - - def wrapper(x): - return alternative(x.values) - - result0 = f(axis=0, skipna=False) - result1 = f(axis=1, skipna=False) - tm.assert_series_equal(result0, frame.apply(wrapper)) - tm.assert_series_equal(result1, frame.apply(wrapper, axis=1), - check_dtype=False) # HACK: win32 - else: - skipna_wrapper = alternative - wrapper = alternative - - result0 = f(axis=0) - result1 = f(axis=1) - tm.assert_series_equal(result0, frame.apply(skipna_wrapper)) - tm.assert_series_equal(result1, frame.apply(skipna_wrapper, axis=1), - check_dtype=False) - - # result = f(axis=1) - # comp = frame.apply(alternative, axis=1).reindex(result.index) - # assert_series_equal(result, comp) - - # bad axis - pytest.raises(ValueError, f, axis=2) - - # make sure works on mixed-type frame - mixed = self.mixed_frame - mixed['_bool_'] = np.random.randn(len(mixed)) > 0 - getattr(mixed, name)(axis=0) - getattr(mixed, name)(axis=1) - - class NonzeroFail(object): - - def __nonzero__(self): - raise ValueError - - mixed['_nonzero_fail_'] = NonzeroFail() - - if has_bool_only: - getattr(mixed, name)(axis=0, bool_only=True) - getattr(mixed, name)(axis=1, bool_only=True) - getattr(frame, name)(axis=0, bool_only=False) - getattr(frame, name)(axis=1, bool_only=False) - - # all NA case - if has_skipna: - all_na = frame * np.NaN - r0 = getattr(all_na, name)(axis=0) - r1 = getattr(all_na, name)(axis=1) - if name == 'any': - assert not r0.any() - assert not r1.any() - else: - assert r0.all() - assert r1.all() - # ---------------------------------------------------------------------- # Isin @@ -1746,34 +1748,34 @@ def test_pct_change(self): # Clip - def test_clip(self): - median = self.frame.median().median() - original = self.frame.copy() + def test_clip(self, float_frame): + median = float_frame.median().median() + original = float_frame.copy() - capped = self.frame.clip_upper(median) + capped = float_frame.clip_upper(median) assert not (capped.values > median).any() - floored = self.frame.clip_lower(median) + floored = float_frame.clip_lower(median) assert not (floored.values < median).any() - double = self.frame.clip(upper=median, lower=median) + double = float_frame.clip(upper=median, lower=median) assert not (double.values != median).any() - # Verify that self.frame was not changed inplace - assert (self.frame.values == original.values).all() + # Verify that float_frame was not changed inplace + assert (float_frame.values == original.values).all() - def test_inplace_clip(self): + def test_inplace_clip(self, float_frame): # GH #15388 - median = self.frame.median().median() - frame_copy = self.frame.copy() + median = float_frame.median().median() + frame_copy = float_frame.copy() frame_copy.clip_upper(median, inplace=True) assert not (frame_copy.values > median).any() - frame_copy = self.frame.copy() + frame_copy = float_frame.copy() frame_copy.clip_lower(median, inplace=True) assert not (frame_copy.values < median).any() - frame_copy = self.frame.copy() + frame_copy = float_frame.copy() frame_copy.clip(upper=median, lower=median, inplace=True) assert not (frame_copy.values != median).any() @@ -1839,9 +1841,10 @@ def test_clip_against_series(self, inplace): (0, [[2., 2., 3.], [4., 5., 6.], [7., 7., 7.]]), (1, [[2., 3., 4.], [4., 5., 6.], [5., 6., 7.]]) ]) - def test_clip_against_list_like(self, inplace, lower, axis, res): + def test_clip_against_list_like(self, simple_frame, + inplace, lower, axis, res): # GH #15390 - original = self.simple.copy(deep=True) + original = simple_frame.copy(deep=True) result = original.clip(lower=lower, upper=[5, 6, 7], axis=axis, inplace=inplace) @@ -1869,12 +1872,12 @@ def test_clip_against_frame(self, axis): tm.assert_frame_equal(clipped_df[ub_mask], ub[ub_mask]) tm.assert_frame_equal(clipped_df[mask], df[mask]) - def test_clip_with_na_args(self): + def test_clip_with_na_args(self, float_frame): """Should process np.nan argument as None """ # GH # 17276 - tm.assert_frame_equal(self.frame.clip(np.nan), self.frame) - tm.assert_frame_equal(self.frame.clip(upper=np.nan, lower=np.nan), - self.frame) + tm.assert_frame_equal(float_frame.clip(np.nan), float_frame) + tm.assert_frame_equal(float_frame.clip(upper=np.nan, lower=np.nan), + float_frame) # GH #19992 df = DataFrame({'col_0': [1, 2, 3], 'col_1': [4, 5, 6], @@ -1919,8 +1922,8 @@ def test_dot(self): row = a.iloc[0].values result = a.dot(row) - exp = a.dot(a.iloc[0]) - tm.assert_series_equal(result, exp) + expected = a.dot(a.iloc[0]) + tm.assert_series_equal(result, expected) with tm.assert_raises_regex(ValueError, 'Dot product shape mismatch'):