From acf5f2f2f82cf357f0ca82fc3830e0831d079762 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 12 Sep 2019 15:42:58 -0500 Subject: [PATCH 01/33] CLN: Remove sparse --- pandas/__init__.py | 2 - pandas/core/arrays/sparse.py | 10 +- pandas/core/dtypes/generic.py | 7 +- pandas/core/groupby/generic.py | 7 - pandas/core/ops/__init__.py | 3 - pandas/core/ops/methods.py | 23 +- pandas/core/reshape/concat.py | 18 +- pandas/core/series.py | 3 +- pandas/core/sparse/api.py | 5 +- pandas/core/sparse/frame.py | 1060 ----------- pandas/core/sparse/series.py | 635 ------- pandas/io/packers.py | 96 +- pandas/io/pytables.py | 88 +- .../tests/arrays/sparse/test_arithmetics.py | 42 - pandas/tests/arrays/sparse/test_array.py | 32 +- pandas/tests/dtypes/test_generic.py | 2 - pandas/tests/sparse/frame/__init__.py | 0 pandas/tests/sparse/frame/conftest.py | 120 -- pandas/tests/sparse/frame/test_analytics.py | 41 - pandas/tests/sparse/frame/test_apply.py | 117 -- pandas/tests/sparse/frame/test_frame.py | 1596 ----------------- pandas/tests/sparse/frame/test_indexing.py | 103 -- pandas/tests/sparse/frame/test_to_csv.py | 24 - .../tests/sparse/frame/test_to_from_scipy.py | 196 -- pandas/tests/sparse/series/__init__.py | 0 pandas/tests/sparse/series/test_indexing.py | 113 -- pandas/tests/sparse/series/test_series.py | 1596 ----------------- pandas/tests/sparse/test_combine_concat.py | 443 ----- pandas/tests/sparse/test_format.py | 165 -- pandas/tests/sparse/test_groupby.py | 73 - pandas/tests/sparse/test_indexing.py | 1058 ----------- pandas/tests/sparse/test_pivot.py | 59 - pandas/tests/sparse/test_reshape.py | 43 - pandas/util/testing.py | 24 - 34 files changed, 47 insertions(+), 7757 deletions(-) delete mode 100644 pandas/tests/sparse/frame/__init__.py delete mode 100644 pandas/tests/sparse/frame/conftest.py delete mode 100644 pandas/tests/sparse/frame/test_analytics.py delete mode 100644 pandas/tests/sparse/frame/test_apply.py delete mode 100644 pandas/tests/sparse/frame/test_frame.py delete mode 100644 pandas/tests/sparse/frame/test_indexing.py delete mode 100644 pandas/tests/sparse/frame/test_to_csv.py delete mode 100644 pandas/tests/sparse/frame/test_to_from_scipy.py delete mode 100644 pandas/tests/sparse/series/__init__.py delete mode 100644 pandas/tests/sparse/series/test_indexing.py delete mode 100644 pandas/tests/sparse/series/test_series.py diff --git a/pandas/__init__.py b/pandas/__init__.py index 6351b508fb0e5..17706f6a8d16c 100644 --- a/pandas/__init__.py +++ b/pandas/__init__.py @@ -116,8 +116,6 @@ from pandas.core.sparse.api import ( SparseArray, - SparseDataFrame, - SparseSeries, SparseDtype, ) diff --git a/pandas/core/arrays/sparse.py b/pandas/core/arrays/sparse.py index 201174b6b1995..0ada8035b4de5 100644 --- a/pandas/core/arrays/sparse.py +++ b/pandas/core/arrays/sparse.py @@ -43,7 +43,6 @@ ABCIndexClass, ABCSeries, ABCSparseArray, - ABCSparseSeries, ) from pandas.core.dtypes.missing import isna, na_value_for_dtype, notna @@ -607,7 +606,7 @@ def __init__( if fill_value is None and isinstance(dtype, SparseDtype): fill_value = dtype.fill_value - if isinstance(data, (type(self), ABCSparseSeries)): + if isinstance(data, type(self)): # disable normal inference on dtype, sparse_index, & fill_value if sparse_index is None: sparse_index = data.sp_index @@ -1982,7 +1981,8 @@ def from_coo(cls, A, dense_index=False): Returns ------- - s : SparseSeries + s : Series + A Series with sparse values. Examples -------- @@ -1996,7 +1996,7 @@ def from_coo(cls, A, dense_index=False): matrix([[ 0., 0., 1., 2.], [ 3., 0., 0., 0.], [ 0., 0., 0., 0.]]) - >>> ss = pd.SparseSeries.from_coo(A) + >>> ss = pd.Series.sparse.from_coo(A) >>> ss 0 2 1 3 2 @@ -2016,7 +2016,7 @@ def from_coo(cls, A, dense_index=False): def to_coo(self, row_levels=(0,), column_levels=(1,), sort_labels=False): """ - Create a scipy.sparse.coo_matrix from a SparseSeries with MultiIndex. + Create a scipy.sparse.coo_matrix from a Series with MultiIndex. Use row_levels and column_levels to determine the row and column coordinates respectively. row_levels and column_levels are the names diff --git a/pandas/core/dtypes/generic.py b/pandas/core/dtypes/generic.py index de41644f09b66..2518f330b26a3 100644 --- a/pandas/core/dtypes/generic.py +++ b/pandas/core/dtypes/generic.py @@ -52,12 +52,7 @@ def _check(cls, inst): ABCSeries = create_pandas_abc_type("ABCSeries", "_typ", ("series",)) ABCDataFrame = create_pandas_abc_type("ABCDataFrame", "_typ", ("dataframe",)) -ABCSparseDataFrame = create_pandas_abc_type( - "ABCSparseDataFrame", "_subtyp", ("sparse_frame",) -) -ABCSparseSeries = create_pandas_abc_type( - "ABCSparseSeries", "_subtyp", ("sparse_series", "sparse_time_series") -) + ABCSparseArray = create_pandas_abc_type( "ABCSparseArray", "_subtyp", ("sparse_array", "sparse_series") ) diff --git a/pandas/core/groupby/generic.py b/pandas/core/groupby/generic.py index e731cffea0671..f8f1455561c03 100644 --- a/pandas/core/groupby/generic.py +++ b/pandas/core/groupby/generic.py @@ -58,7 +58,6 @@ import pandas.core.indexes.base as ibase from pandas.core.internals import BlockManager, make_block from pandas.core.series import Series -from pandas.core.sparse.frame import SparseDataFrame from pandas.plotting import boxplot_frame_groupby @@ -258,12 +257,6 @@ def aggregate(self, func, *args, **kwargs): result.columns.levels[0], name=self._selected_obj.columns.name ) - if isinstance(self.obj, SparseDataFrame): - # Backwards compat for groupby.agg() with sparse - # values. concat no longer converts DataFrame[Sparse] - # to SparseDataFrame, so we do it here. - result = SparseDataFrame(result._data) - if not self.as_index: self._insert_inaxis_grouper_inplace(result) result.index = np.arange(len(result)) diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index 016feff7e3beb..3e56cb351273a 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -32,7 +32,6 @@ ABCExtensionArray, ABCIndexClass, ABCSeries, - ABCSparseSeries, ABCTimedeltaArray, ABCTimedeltaIndex, ) @@ -1151,8 +1150,6 @@ def wrapper(self, other): if isinstance(other, ABCDataFrame): return NotImplemented elif isinstance(other, ABCSeries): - if not isinstance(other, ABCSparseSeries): - other = other.to_sparse(fill_value=self.fill_value) return _sparse_series_op(self, other, op, op_name) elif is_scalar(other): with np.errstate(all="ignore"): diff --git a/pandas/core/ops/methods.py b/pandas/core/ops/methods.py index eba0a797a791f..3dabb1075f03d 100644 --- a/pandas/core/ops/methods.py +++ b/pandas/core/ops/methods.py @@ -3,12 +3,7 @@ """ import operator -from pandas.core.dtypes.generic import ( - ABCDataFrame, - ABCSeries, - ABCSparseArray, - ABCSparseSeries, -) +from pandas.core.dtypes.generic import ABCDataFrame, ABCSeries, ABCSparseArray from pandas.core.ops.roperator import ( radd, @@ -51,7 +46,6 @@ def _get_method_wrappers(cls): from pandas.core.ops import ( _arith_method_FRAME, _arith_method_SERIES, - _arith_method_SPARSE_SERIES, _bool_method_SERIES, _comp_method_FRAME, _comp_method_SERIES, @@ -59,17 +53,8 @@ def _get_method_wrappers(cls): _flex_method_SERIES, ) - if issubclass(cls, ABCSparseSeries): - # Be sure to catch this before ABCSeries and ABCSparseArray, - # as they will both come see SparseSeries as a subclass - arith_flex = _flex_method_SERIES - comp_flex = _flex_method_SERIES - arith_special = _arith_method_SPARSE_SERIES - comp_special = _arith_method_SPARSE_SERIES - bool_special = _bool_method_SERIES - # TODO: I don't think the functions defined by bool_method are tested - elif issubclass(cls, ABCSeries): - # Just Series; SparseSeries is caught above + if issubclass(cls, ABCSeries): + # Just Series arith_flex = _flex_method_SERIES comp_flex = _flex_method_SERIES arith_special = _arith_method_SERIES @@ -176,7 +161,7 @@ def _create_methods(cls, arith_method, comp_method, bool_method, special): # constructors. have_divmod = issubclass(cls, ABCSeries) - # divmod is available for Series and SparseSeries + # divmod is available for Series # yapf: disable new_methods = dict( diff --git a/pandas/core/reshape/concat.py b/pandas/core/reshape/concat.py index 4446f27da6be0..8ae9a1253e79c 100644 --- a/pandas/core/reshape/concat.py +++ b/pandas/core/reshape/concat.py @@ -6,8 +6,6 @@ import numpy as np -from pandas.core.dtypes.generic import ABCSparseDataFrame - from pandas import DataFrame, Index, MultiIndex, Series from pandas.core import common as com from pandas.core.arrays.categorical import ( @@ -715,15 +713,11 @@ def _get_series_result_type(result, objs=None): return appropriate class of Series concat input is either dict or array-like """ - from pandas import SparseSeries, SparseDataFrame, DataFrame + from pandas import DataFrame # concat Series with axis 1 if isinstance(result, dict): - # concat Series with axis 1 - if all(isinstance(c, (SparseSeries, SparseDataFrame)) for c in result.values()): - return SparseDataFrame - else: - return DataFrame + return DataFrame # otherwise it is a SingleBlockManager (axis = 0) return objs[0]._constructor @@ -735,10 +729,4 @@ def _get_frame_result_type(result, objs): if all blocks are sparse, return SparseDataFrame otherwise, return 1st obj """ - - if result.blocks and (any(isinstance(obj, ABCSparseDataFrame) for obj in objs)): - from pandas.core.sparse.api import SparseDataFrame - - return SparseDataFrame - else: - return next(obj for obj in objs if not isinstance(obj, ABCSparseDataFrame)) + return objs[0] diff --git a/pandas/core/series.py b/pandas/core/series.py index 10d50e89ca92e..676f9a50a4e8b 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -44,7 +44,6 @@ ABCDatetimeIndex, ABCSeries, ABCSparseArray, - ABCSparseSeries, ) from pandas.core.dtypes.missing import ( isna, @@ -246,7 +245,7 @@ def __init__( elif isinstance(data, np.ndarray): pass - elif isinstance(data, (ABCSeries, ABCSparseSeries)): + elif isinstance(data, ABCSeries): if name is None: name = data.name if index is None: diff --git a/pandas/core/sparse/api.py b/pandas/core/sparse/api.py index 6a00fa570b2ac..e7bf94cdc08ea 100644 --- a/pandas/core/sparse/api.py +++ b/pandas/core/sparse/api.py @@ -1,4 +1,3 @@ -# flake8: noqa from pandas.core.arrays.sparse import SparseArray, SparseDtype -from pandas.core.sparse.frame import SparseDataFrame -from pandas.core.sparse.series import SparseSeries + +__all__ = ["SparseArray", "SparseDtype"] diff --git a/pandas/core/sparse/frame.py b/pandas/core/sparse/frame.py index 3d6ba0b8d9774..e69de29bb2d1d 100644 --- a/pandas/core/sparse/frame.py +++ b/pandas/core/sparse/frame.py @@ -1,1060 +0,0 @@ -""" -Data structures for sparse float data. Life is made simpler by dealing only -with float64 data -""" -import warnings - -import numpy as np - -from pandas._libs.lib import is_scalar, item_from_zerodim -from pandas._libs.sparse import BlockIndex, get_blocks -from pandas.compat.numpy import function as nv -from pandas.util._decorators import Appender - -from pandas.core.dtypes.cast import maybe_upcast -from pandas.core.dtypes.common import ensure_platform_int, is_scipy_sparse -from pandas.core.dtypes.missing import isna, notna - -import pandas.core.algorithms as algos -from pandas.core.arrays.sparse import SparseArray, SparseFrameAccessor -import pandas.core.common as com -from pandas.core.frame import DataFrame -import pandas.core.generic as generic -from pandas.core.index import Index, MultiIndex, ensure_index -from pandas.core.internals import BlockManager, create_block_manager_from_arrays -from pandas.core.internals.construction import extract_index, prep_ndarray -import pandas.core.ops as ops -from pandas.core.series import Series -from pandas.core.sparse.series import SparseSeries - -_shared_doc_kwargs = dict(klass="SparseDataFrame") -depr_msg = """\ -SparseDataFrame is deprecated and will be removed in a future version. -Use a regular DataFrame whose columns are SparseArrays instead. - -See http://pandas.pydata.org/pandas-docs/stable/\ -user_guide/sparse.html#migrating for more. -""" - - -class SparseDataFrame(DataFrame): - """ - DataFrame containing sparse floating point data in the form of SparseSeries - objects - - .. deprecated:: 0.25.0 - - Use a DataFrame with sparse values instead. - - Parameters - ---------- - data : same types as can be passed to DataFrame or scipy.sparse.spmatrix - .. versionchanged:: 0.23.0 - If data is a dict, argument order is maintained for Python 3.6 - and later. - - index : array-like, optional - column : array-like, optional - default_kind : {'block', 'integer'}, default 'block' - Default sparse kind for converting Series to SparseSeries. Will not - override SparseSeries passed into constructor - default_fill_value : float - Default fill_value for converting Series to SparseSeries - (default: nan). Will not override SparseSeries passed in. - """ - - _subtyp = "sparse_frame" - - def __init__( - self, - data=None, - index=None, - columns=None, - default_kind=None, - default_fill_value=None, - dtype=None, - copy=False, - ): - if not is_scalar(default_fill_value): - raise ValueError("'default_fill_value' must be a scalar") - - warnings.warn(depr_msg, FutureWarning, stacklevel=2) - # pick up the defaults from the Sparse structures - if isinstance(data, SparseDataFrame): - if index is None: - index = data.index - if columns is None: - columns = data.columns - if default_fill_value is None: - default_fill_value = data.default_fill_value - if default_kind is None: - default_kind = data.default_kind - elif isinstance(data, (SparseSeries, SparseArray)): - if index is None: - index = data.index - if default_fill_value is None: - default_fill_value = data.fill_value - if columns is None and hasattr(data, "name"): - columns = [data.name] - if columns is None: - raise Exception("cannot pass a series w/o a name or columns") - data = {columns[0]: data} - - if default_fill_value is None: - default_fill_value = np.nan - if default_kind is None: - default_kind = "block" - - self._default_kind = default_kind - self._default_fill_value = default_fill_value - - if is_scipy_sparse(data): - mgr = self._init_spmatrix( - data, index, columns, dtype=dtype, fill_value=default_fill_value - ) - elif isinstance(data, dict): - mgr = self._init_dict(data, index, columns, dtype=dtype) - elif isinstance(data, (np.ndarray, list)): - mgr = self._init_matrix(data, index, columns, dtype=dtype) - elif isinstance(data, SparseDataFrame): - mgr = self._init_mgr( - data._data, dict(index=index, columns=columns), dtype=dtype, copy=copy - ) - elif isinstance(data, DataFrame): - mgr = self._init_dict(data, data.index, data.columns, dtype=dtype) - elif isinstance(data, Series): - mgr = self._init_dict( - data.to_frame(), data.index, columns=None, dtype=dtype - ) - elif isinstance(data, BlockManager): - mgr = self._init_mgr( - data, axes=dict(index=index, columns=columns), dtype=dtype, copy=copy - ) - elif data is None: - data = DataFrame() - - if index is None: - index = Index([]) - else: - index = ensure_index(index) - - if columns is None: - columns = Index([]) - else: - for c in columns: - data[c] = SparseArray( - self._default_fill_value, - index=index, - kind=self._default_kind, - fill_value=self._default_fill_value, - ) - mgr = to_manager(data, columns, index) - if dtype is not None: - mgr = mgr.astype(dtype) - else: - msg = ( - 'SparseDataFrame called with unknown type "{data_type}" ' - "for data argument" - ) - raise TypeError(msg.format(data_type=type(data).__name__)) - - generic.NDFrame.__init__(self, mgr) - - @property - def _constructor(self): - return SparseDataFrame - - _constructor_sliced = SparseSeries - - def _init_dict(self, data, index, columns, dtype=None): - # pre-filter out columns if we passed it - if columns is not None: - columns = ensure_index(columns) - data = {k: v for k, v in data.items() if k in columns} - else: - keys = com.dict_keys_to_ordered_list(data) - columns = Index(keys) - - if index is None: - index = extract_index(list(data.values())) - - def sp_maker(x): - return SparseArray( - x, - kind=self._default_kind, - fill_value=self._default_fill_value, - copy=True, - dtype=dtype, - ) - - sdict = {} - for k, v in data.items(): - if isinstance(v, Series): - # Force alignment, no copy necessary - if not v.index.equals(index): - v = v.reindex(index) - - if not isinstance(v, SparseSeries): - v = sp_maker(v.values) - elif isinstance(v, SparseArray): - v = v.copy() - else: - if isinstance(v, dict): - v = [v.get(i, np.nan) for i in index] - - v = sp_maker(v) - - if index is not None and len(v) != len(index): - msg = "Length of passed values is {}, index implies {}" - raise ValueError(msg.format(len(v), len(index))) - sdict[k] = v - - if len(columns.difference(sdict)): - # TODO: figure out how to handle this case, all nan's? - # add in any other columns we want to have (completeness) - nan_arr = np.empty(len(index), dtype="float64") - nan_arr.fill(np.nan) - nan_arr = SparseArray( - nan_arr, - kind=self._default_kind, - fill_value=self._default_fill_value, - copy=False, - ) - sdict.update((c, nan_arr) for c in columns if c not in sdict) - - return to_manager(sdict, columns, index) - - def _init_matrix(self, data, index, columns, dtype=None): - """ - Init self from ndarray or list of lists. - """ - data = prep_ndarray(data, copy=False) - index, columns = SparseFrameAccessor._prep_index(data, index, columns) - data = {idx: data[:, i] for i, idx in enumerate(columns)} - return self._init_dict(data, index, columns, dtype) - - def _init_spmatrix(self, data, index, columns, dtype=None, fill_value=None): - """ - Init self from scipy.sparse matrix. - """ - index, columns = SparseFrameAccessor._prep_index(data, index, columns) - data = data.tocoo() - N = len(index) - - # Construct a dict of SparseSeries - sdict = {} - values = Series(data.data, index=data.row, copy=False) - for col, rowvals in values.groupby(data.col): - # get_blocks expects int32 row indices in sorted order - rowvals = rowvals.sort_index() - rows = rowvals.index.values.astype(np.int32) - blocs, blens = get_blocks(rows) - - sdict[columns[col]] = SparseSeries( - rowvals.values, - index=index, - fill_value=fill_value, - sparse_index=BlockIndex(N, blocs, blens), - ) - - # Add any columns that were empty and thus not grouped on above - sdict.update( - { - column: SparseSeries( - index=index, - fill_value=fill_value, - sparse_index=BlockIndex(N, [], []), - ) - for column in columns - if column not in sdict - } - ) - - return self._init_dict(sdict, index, columns, dtype) - - @Appender(SparseFrameAccessor.to_coo.__doc__) - def to_coo(self): - return SparseFrameAccessor(self).to_coo() - - def __repr__(self): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", "Sparse") - return super().__repr__() - - def __getstate__(self): - # pickling - return dict( - _typ=self._typ, - _subtyp=self._subtyp, - _data=self._data, - _default_fill_value=self._default_fill_value, - _default_kind=self._default_kind, - ) - - def _unpickle_sparse_frame_compat(self, state): - """ - Original pickle format - """ - series, cols, idx, fv, kind = state - - if not isinstance(cols, Index): # pragma: no cover - from pandas.io.pickle import _unpickle_array - - columns = _unpickle_array(cols) - else: - columns = cols - - if not isinstance(idx, Index): # pragma: no cover - from pandas.io.pickle import _unpickle_array - - index = _unpickle_array(idx) - else: - index = idx - - series_dict = DataFrame() - for col, (sp_index, sp_values) in series.items(): - series_dict[col] = SparseSeries( - sp_values, sparse_index=sp_index, fill_value=fv - ) - - self._data = to_manager(series_dict, columns, index) - self._default_fill_value = fv - self._default_kind = kind - - @Appender(SparseFrameAccessor.to_dense.__doc__) - def to_dense(self): - return SparseFrameAccessor(self).to_dense() - - def _apply_columns(self, func): - """ - Get new SparseDataFrame applying func to each columns - """ - - new_data = {col: func(series) for col, series in self.items()} - - return self._constructor( - data=new_data, - index=self.index, - columns=self.columns, - default_fill_value=self.default_fill_value, - ).__finalize__(self) - - def astype(self, dtype): - return self._apply_columns(lambda x: x.astype(dtype)) - - def copy(self, deep=True): - """ - Make a copy of this SparseDataFrame - """ - result = super().copy(deep=deep) - result._default_fill_value = self._default_fill_value - result._default_kind = self._default_kind - return result - - @property - def default_fill_value(self): - return self._default_fill_value - - @property - def default_kind(self): - return self._default_kind - - @property - def density(self): - """ - Ratio of non-sparse points to total (dense) data points - represented in the frame - """ - tot_nonsparse = sum(ser.sp_index.npoints for _, ser in self.items()) - tot = len(self.index) * len(self.columns) - return tot_nonsparse / float(tot) - - def fillna( - self, value=None, method=None, axis=0, inplace=False, limit=None, downcast=None - ): - new_self = super().fillna( - value=value, - method=method, - axis=axis, - inplace=inplace, - limit=limit, - downcast=downcast, - ) - if not inplace: - self = new_self - - # set the fill value if we are filling as a scalar with nothing special - # going on - if value is not None and value == value and method is None and limit is None: - self._default_fill_value = value - - if not inplace: - return self - - # ---------------------------------------------------------------------- - # Support different internal representation of SparseDataFrame - - def _sanitize_column(self, key, value, **kwargs): - """ - Creates a new SparseArray from the input value. - - Parameters - ---------- - key : object - value : scalar, Series, or array-like - kwargs : dict - - Returns - ------- - sanitized_column : SparseArray - - """ - - def sp_maker(x, index=None): - return SparseArray( - x, - index=index, - fill_value=self._default_fill_value, - kind=self._default_kind, - ) - - if isinstance(value, SparseSeries): - clean = value.reindex(self.index).as_sparse_array( - fill_value=self._default_fill_value, kind=self._default_kind - ) - - elif isinstance(value, SparseArray): - if len(value) != len(self.index): - raise ValueError("Length of values does not match length of index") - clean = value - - elif hasattr(value, "__iter__"): - if isinstance(value, Series): - clean = value.reindex(self.index) - if not isinstance(value, SparseSeries): - clean = sp_maker(clean) - else: - if len(value) != len(self.index): - raise ValueError("Length of values does not match length of index") - clean = sp_maker(value) - - # Scalar - else: - clean = sp_maker(value, self.index) - - # always return a SparseArray! - return clean - - # ---------------------------------------------------------------------- - # Indexing Methods - - def _get_value(self, index, col, takeable=False): - """ - Quickly retrieve single value at passed column and index - - Please use .at[] or .iat[] accessors. - - Parameters - ---------- - index : row label - col : column label - takeable : interpret the index/col as indexers, default False - - Returns - ------- - value : scalar value - """ - if takeable is True: - series = self._iget_item_cache(col) - else: - series = self._get_item_cache(col) - - return series._get_value(index, takeable=takeable) - - def _slice(self, slobj, axis=0, kind=None): - if axis == 0: - new_index = self.index[slobj] - new_columns = self.columns - else: - new_index = self.index - new_columns = self.columns[slobj] - - return self.reindex(index=new_index, columns=new_columns) - - def xs(self, key, axis=0, copy=False): - """ - Returns a row (cross-section) from the SparseDataFrame as a Series - object. - - Parameters - ---------- - key : some index contained in the index - - Returns - ------- - xs : Series - """ - if axis == 1: - data = self[key] - return data - - i = self.index.get_loc(key) - data = self.take([i])._internal_get_values()[0] - return Series(data, index=self.columns) - - def _set_value(self, index, col, value, takeable=False): - """ - Put single value at passed column and index - - Please use .at[] or .iat[] accessors. - - Parameters - ---------- - index : row label - col : column label - value : scalar value - takeable : interpret the index/col as indexers, default False - - Notes - ----- - This method *always* returns a new object. It is currently not - particularly efficient (and potentially very expensive) but is provided - for API compatibility with DataFrame - - Returns - ------- - frame : DataFrame - """ - dense = self.to_dense()._set_value(index, col, value, takeable=takeable) - return dense.to_sparse( - kind=self._default_kind, fill_value=self._default_fill_value - ) - - # ---------------------------------------------------------------------- - # Arithmetic-related methods - - def _combine_frame(self, other, func, fill_value=None, level=None): - if level is not None: - raise NotImplementedError("'level' argument is not supported") - - this, other = self.align(other, join="outer", level=level, copy=False) - new_index, new_columns = this.index, this.columns - - if self.empty and other.empty: - return self._constructor(index=new_index).__finalize__(self) - - new_data = {} - if fill_value is not None: - # TODO: be a bit more intelligent here - for col in new_columns: - if col in this and col in other: - dleft = this[col].to_dense() - dright = other[col].to_dense() - result = dleft._binop(dright, func, fill_value=fill_value) - result = result.to_sparse(fill_value=this[col].fill_value) - new_data[col] = result - else: - - for col in new_columns: - if col in this and col in other: - new_data[col] = func(this[col], other[col]) - - new_fill_value = self._get_op_result_fill_value(other, func) - - return self._constructor( - data=new_data, - index=new_index, - columns=new_columns, - default_fill_value=new_fill_value, - ).__finalize__(self) - - def _combine_match_index(self, other, func, level=None): - - if level is not None: - raise NotImplementedError("'level' argument is not supported") - - this, other = self.align(other, join="outer", axis=0, level=level, copy=False) - - new_data = {} - for col in this.columns: - new_data[col] = func(this[col], other) - - fill_value = self._get_op_result_fill_value(other, func) - - return self._constructor( - new_data, - index=this.index, - columns=self.columns, - default_fill_value=fill_value, - ).__finalize__(self) - - def _combine_match_columns(self, other, func, level=None): - # patched version of DataFrame._combine_match_columns to account for - # NumPy circumventing __rsub__ with float64 types, e.g.: 3.0 - series, - # where 3.0 is numpy.float64 and series is a SparseSeries. Still - # possible for this to happen, which is bothersome - - if level is not None: - raise NotImplementedError("'level' argument is not supported") - - left, right = self.align(other, join="outer", axis=1, level=level, copy=False) - assert left.columns.equals(right.index) - - new_data = {} - - for col in left.columns: - new_data[col] = func(left[col], right[col]) - - return self._constructor( - new_data, - index=left.index, - columns=left.columns, - default_fill_value=self.default_fill_value, - ).__finalize__(self) - - def _combine_const(self, other, func): - return self._apply_columns(lambda x: func(x, other)) - - def _get_op_result_fill_value(self, other, func): - own_default = self.default_fill_value - - if isinstance(other, DataFrame): - # i.e. called from _combine_frame - - other_default = getattr(other, "default_fill_value", np.nan) - - # if the fill values are the same use them? or use a valid one - if own_default == other_default: - # TOOD: won't this evaluate as False if both are np.nan? - fill_value = own_default - elif np.isnan(own_default) and not np.isnan(other_default): - fill_value = other_default - elif not np.isnan(own_default) and np.isnan(other_default): - fill_value = own_default - else: - fill_value = None - - elif isinstance(other, SparseSeries): - # i.e. called from _combine_match_index - - # fill_value is a function of our operator - if isna(other.fill_value) or isna(own_default): - fill_value = np.nan - else: - fill_value = func(np.float64(own_default), np.float64(other.fill_value)) - fill_value = item_from_zerodim(fill_value) - else: - raise NotImplementedError(type(other)) - - return fill_value - - def _reindex_index( - self, index, method, copy, level, fill_value=np.nan, limit=None, takeable=False - ): - if level is not None: - raise TypeError("Reindex by level not supported for sparse") - - if self.index.equals(index): - if copy: - return self.copy() - else: - return self - - if len(self.index) == 0: - return self._constructor(index=index, columns=self.columns).__finalize__( - self - ) - - indexer = self.index.get_indexer(index, method, limit=limit) - indexer = ensure_platform_int(indexer) - mask = indexer == -1 - need_mask = mask.any() - - new_series = {} - for col, series in self.items(): - if mask.all(): - continue - - values = series.values - # .take returns SparseArray - new = values.take(indexer) - if need_mask: - new = new.to_dense() - # convert integer to float if necessary. need to do a lot - # more than that, handle boolean etc also - new, fill_value = maybe_upcast(new, fill_value=fill_value) - np.putmask(new, mask, fill_value) - - new_series[col] = new - - return self._constructor( - new_series, - index=index, - columns=self.columns, - default_fill_value=self._default_fill_value, - ).__finalize__(self) - - def _reindex_columns( - self, columns, method, copy, level, fill_value=None, limit=None, takeable=False - ): - if level is not None: - raise TypeError("Reindex by level not supported for sparse") - - if notna(fill_value): - raise NotImplementedError("'fill_value' argument is not supported") - - if limit: - raise NotImplementedError("'limit' argument is not supported") - - if method is not None: - raise NotImplementedError("'method' argument is not supported") - - # TODO: fill value handling - sdict = {k: v for k, v in self.items() if k in columns} - return self._constructor( - sdict, - index=self.index, - columns=columns, - default_fill_value=self._default_fill_value, - ).__finalize__(self) - - def _reindex_with_indexers( - self, - reindexers, - method=None, - fill_value=None, - limit=None, - copy=False, - allow_dups=False, - ): - - if method is not None or limit is not None: - raise NotImplementedError( - "cannot reindex with a method or limit with sparse" - ) - - if fill_value is None: - fill_value = np.nan - - reindexers = {self._get_axis_number(a): val for (a, val) in reindexers.items()} - - index, row_indexer = reindexers.get(0, (None, None)) - columns, col_indexer = reindexers.get(1, (None, None)) - - if columns is None: - columns = self.columns - - new_arrays = {} - for col in columns: - if col not in self: - continue - if row_indexer is not None: - new_arrays[col] = algos.take_1d( - self[col]._internal_get_values(), row_indexer, fill_value=fill_value - ) - else: - new_arrays[col] = self[col] - - return self._constructor(new_arrays, index=index, columns=columns).__finalize__( - self - ) - - def _join_compat( - self, other, on=None, how="left", lsuffix="", rsuffix="", sort=False - ): - if on is not None: - raise NotImplementedError("'on' keyword parameter is not yet implemented") - return self._join_index(other, how, lsuffix, rsuffix) - - def _join_index(self, other, how, lsuffix, rsuffix): - if isinstance(other, Series): - if other.name is None: - raise ValueError("Other Series must have a name") - - other = SparseDataFrame( - {other.name: other}, default_fill_value=self._default_fill_value - ) - - join_index = self.index.join(other.index, how=how) - - this = self.reindex(join_index) - other = other.reindex(join_index) - - this, other = this._maybe_rename_join(other, lsuffix, rsuffix) - - from pandas import concat - - return concat([this, other], axis=1, verify_integrity=True) - - def _maybe_rename_join(self, other, lsuffix, rsuffix): - to_rename = self.columns.intersection(other.columns) - if len(to_rename) > 0: - if not lsuffix and not rsuffix: - raise ValueError( - "columns overlap but no suffix specified: " - "{to_rename}".format(to_rename=to_rename) - ) - - def lrenamer(x): - if x in to_rename: - return "{x}{lsuffix}".format(x=x, lsuffix=lsuffix) - return x - - def rrenamer(x): - if x in to_rename: - return "{x}{rsuffix}".format(x=x, rsuffix=rsuffix) - return x - - this = self.rename(columns=lrenamer) - other = other.rename(columns=rrenamer) - else: - this = self - - return this, other - - def transpose(self, *args, **kwargs): - """ - Returns a DataFrame with the rows/columns switched. - """ - nv.validate_transpose(args, kwargs) - return self._constructor( - self.values.T, - index=self.columns, - columns=self.index, - default_fill_value=self._default_fill_value, - default_kind=self._default_kind, - ).__finalize__(self) - - T = property(transpose) - - @Appender(DataFrame.count.__doc__) - def count(self, axis=0, **kwds): - if axis is None: - axis = self._stat_axis_number - - return self.apply(lambda x: x.count(), axis=axis) - - def cumsum(self, axis=0, *args, **kwargs): - """ - Return SparseDataFrame of cumulative sums over requested axis. - - Parameters - ---------- - axis : {0, 1} - 0 for row-wise, 1 for column-wise - - Returns - ------- - y : SparseDataFrame - """ - nv.validate_cumsum(args, kwargs) - - if axis is None: - axis = self._stat_axis_number - - return self.apply(lambda x: x.cumsum(), axis=axis) - - @Appender(generic._shared_docs["isna"] % _shared_doc_kwargs) - def isna(self): - return self._apply_columns(lambda x: x.isna()) - - isnull = isna - - @Appender(generic._shared_docs["notna"] % _shared_doc_kwargs) - def notna(self): - return self._apply_columns(lambda x: x.notna()) - - notnull = notna - - def apply(self, func, axis=0, broadcast=None, reduce=None, result_type=None): - """ - Analogous to DataFrame.apply, for SparseDataFrame - - Parameters - ---------- - func : function - Function to apply to each column - axis : {0, 1, 'index', 'columns'} - broadcast : bool, default False - For aggregation functions, return object of same size with values - propagated - - .. deprecated:: 0.23.0 - This argument will be removed in a future version, replaced - by result_type='broadcast'. - - reduce : boolean or None, default None - Try to apply reduction procedures. If the DataFrame is empty, - apply will use reduce to determine whether the result should be a - Series or a DataFrame. If reduce is None (the default), apply's - return value will be guessed by calling func an empty Series (note: - while guessing, exceptions raised by func will be ignored). If - reduce is True a Series will always be returned, and if False a - DataFrame will always be returned. - - .. deprecated:: 0.23.0 - This argument will be removed in a future version, replaced - by result_type='reduce'. - - result_type : {'expand', 'reduce', 'broadcast, None} - These only act when axis=1 {columns}: - - * 'expand' : list-like results will be turned into columns. - * 'reduce' : return a Series if possible rather than expanding - list-like results. This is the opposite to 'expand'. - * 'broadcast' : results will be broadcast to the original shape - of the frame, the original index & columns will be retained. - - The default behaviour (None) depends on the return value of the - applied function: list-like results will be returned as a Series - of those. However if the apply function returns a Series these - are expanded to columns. - - .. versionadded:: 0.23.0 - - Returns - ------- - applied : Series or SparseDataFrame - """ - if not len(self.columns): - return self - axis = self._get_axis_number(axis) - - if isinstance(func, np.ufunc): - new_series = {} - for k, v in self.items(): - applied = func(v) - applied.fill_value = func(v.fill_value) - new_series[k] = applied - return self._constructor( - new_series, - index=self.index, - columns=self.columns, - default_fill_value=self._default_fill_value, - default_kind=self._default_kind, - ).__finalize__(self) - - from pandas.core.apply import frame_apply - - op = frame_apply( - self, - func=func, - axis=axis, - reduce=reduce, - broadcast=broadcast, - result_type=result_type, - ) - return op.get_result() - - def applymap(self, func): - """ - Apply a function to a DataFrame that is intended to operate - elementwise, i.e. like doing map(func, series) for each series in the - DataFrame - - Parameters - ---------- - func : function - Python function, returns a single value from a single value - - Returns - ------- - applied : DataFrame - """ - return self.apply(lambda x: [func(y) for y in x]) - - -def to_manager(sdf, columns, index): - """ create and return the block manager from a dataframe of series, - columns, index - """ - - # from BlockManager perspective - axes = [ensure_index(columns), ensure_index(index)] - - return create_block_manager_from_arrays([sdf[c] for c in columns], columns, axes) - - -def stack_sparse_frame(frame): - """ - Only makes sense when fill_value is NaN - """ - lengths = [s.sp_index.npoints for _, s in frame.items()] - nobs = sum(lengths) - - # this is pretty fast - minor_codes = np.repeat(np.arange(len(frame.columns)), lengths) - - inds_to_concat = [] - vals_to_concat = [] - # TODO: Figure out whether this can be reached. - # I think this currently can't be reached because you can't build a - # SparseDataFrame with a non-np.NaN fill value (fails earlier). - for _, series in frame.items(): - if not np.isnan(series.fill_value): - raise TypeError("This routine assumes NaN fill value") - - int_index = series.sp_index.to_int_index() - inds_to_concat.append(int_index.indices) - vals_to_concat.append(series.sp_values) - - major_codes = np.concatenate(inds_to_concat) - stacked_values = np.concatenate(vals_to_concat) - index = MultiIndex( - levels=[frame.index, frame.columns], - codes=[major_codes, minor_codes], - verify_integrity=False, - ) - - lp = DataFrame(stacked_values.reshape((nobs, 1)), index=index, columns=["foo"]) - return lp.sort_index(level=0) - - -def homogenize(series_dict): - """ - Conform a set of SparseSeries (with NaN fill_value) to a common SparseIndex - corresponding to the locations where they all have data - - Parameters - ---------- - series_dict : dict or DataFrame - - Notes - ----- - Using the dumbest algorithm I could think of. Should put some more thought - into this - - Returns - ------- - homogenized : dict of SparseSeries - """ - index = None - - need_reindex = False - - for _, series in series_dict.items(): - if not np.isnan(series.fill_value): - raise TypeError("this method is only valid with NaN fill values") - - if index is None: - index = series.sp_index - elif not series.sp_index.equals(index): - need_reindex = True - index = index.intersect(series.sp_index) - - if need_reindex: - output = {} - for name, series in series_dict.items(): - if not series.sp_index.equals(index): - series = series.sparse_reindex(index) - - output[name] = series - else: - output = series_dict - - return output - - -# use unaccelerated ops for sparse objects -ops.add_flex_arithmetic_methods(SparseDataFrame) -ops.add_special_arithmetic_methods(SparseDataFrame) diff --git a/pandas/core/sparse/series.py b/pandas/core/sparse/series.py index 0c417133b0538..e69de29bb2d1d 100644 --- a/pandas/core/sparse/series.py +++ b/pandas/core/sparse/series.py @@ -1,635 +0,0 @@ -""" -Data structures for sparse float data. Life is made simpler by dealing only -with float64 data -""" -from collections import abc -import warnings - -import numpy as np - -import pandas._libs.index as libindex -import pandas._libs.sparse as splib -from pandas._libs.sparse import BlockIndex, IntIndex -from pandas.compat.numpy import function as nv -from pandas.util._decorators import Appender, Substitution - -from pandas.core.dtypes.common import is_integer, is_scalar -from pandas.core.dtypes.generic import ABCSeries, ABCSparseSeries -from pandas.core.dtypes.missing import isna, notna - -from pandas.core import generic -from pandas.core.arrays import SparseArray -from pandas.core.arrays.sparse import SparseAccessor -from pandas.core.internals import SingleBlockManager -import pandas.core.ops as ops -from pandas.core.series import Series -from pandas.core.sparse.scipy_sparse import _coo_to_sparse_series, _sparse_series_to_coo - -_shared_doc_kwargs = dict( - axes="index", - klass="SparseSeries", - axes_single_arg="{0, 'index'}", - optional_labels="", - optional_axis="", -) - - -depr_msg = """\ -SparseSeries is deprecated and will be removed in a future version. -Use a Series with sparse values instead. - - >>> series = pd.Series(pd.SparseArray(...)) - -See http://pandas.pydata.org/pandas-docs/stable/\ -user_guide/sparse.html#migrating for more. -""" - - -class SparseSeries(Series): - """Data structure for labeled, sparse floating point data - - .. deprecated:: 0.25.0 - - Use a Series with sparse values instead. - - Parameters - ---------- - data : {array-like, Series, SparseSeries, dict} - .. versionchanged:: 0.23.0 - If data is a dict, argument order is maintained for Python 3.6 - and later. - - kind : {'block', 'integer'} - fill_value : float - Code for missing value. Defaults depends on dtype. - 0 for int dtype, False for bool dtype, and NaN for other dtypes - sparse_index : {BlockIndex, IntIndex}, optional - Only if you have one. Mainly used internally - - Notes - ----- - SparseSeries objects are immutable via the typical Python means. If you - must change values, convert to dense, make your changes, then convert back - to sparse - """ - - _subtyp = "sparse_series" - - def __init__( - self, - data=None, - index=None, - sparse_index=None, - kind="block", - fill_value=None, - name=None, - dtype=None, - copy=False, - fastpath=False, - ): - warnings.warn(depr_msg, FutureWarning, stacklevel=2) - # TODO: Most of this should be refactored and shared with Series - # 1. BlockManager -> array - # 2. Series.index, Series.name, index, name reconciliation - # 3. Implicit reindexing - # 4. Implicit broadcasting - # 5. Dict construction - if data is None: - data = [] - elif isinstance(data, SingleBlockManager): - index = data.index - data = data.blocks[0].values - elif isinstance(data, (ABCSeries, ABCSparseSeries)): - index = data.index if index is None else index - dtype = data.dtype if dtype is None else dtype - name = data.name if name is None else name - - if index is not None: - data = data.reindex(index) - - elif isinstance(data, abc.Mapping): - data, index = Series()._init_dict(data, index=index) - - elif is_scalar(data) and index is not None: - data = np.full(len(index), fill_value=data) - - if isinstance(data, SingleBlockManager): - # SparseArray doesn't accept SingleBlockManager - index = data.index - data = data.blocks[0].values - - super().__init__( - SparseArray( - data, - sparse_index=sparse_index, - kind=kind, - dtype=dtype, - fill_value=fill_value, - copy=copy, - ), - index=index, - name=name, - copy=False, - fastpath=fastpath, - ) - - def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): - # avoid infinite recursion for other SparseSeries inputs - inputs = tuple(x.values if isinstance(x, type(self)) else x for x in inputs) - result = self.values.__array_ufunc__(ufunc, method, *inputs, **kwargs) - return self._constructor( - result, - index=self.index, - sparse_index=self.sp_index, - fill_value=result.fill_value, - copy=False, - ).__finalize__(self) - - # unary ops - # TODO: See if this can be shared - def __pos__(self): - result = self.values.__pos__() - return self._constructor( - result, - index=self.index, - sparse_index=self.sp_index, - fill_value=result.fill_value, - copy=False, - ).__finalize__(self) - - def __neg__(self): - result = self.values.__neg__() - return self._constructor( - result, - index=self.index, - sparse_index=self.sp_index, - fill_value=result.fill_value, - copy=False, - ).__finalize__(self) - - def __invert__(self): - result = self.values.__invert__() - return self._constructor( - result, - index=self.index, - sparse_index=self.sp_index, - fill_value=result.fill_value, - copy=False, - ).__finalize__(self) - - @property - def block(self): - warnings.warn("SparseSeries.block is deprecated.", FutureWarning, stacklevel=2) - return self._data._block - - @property - def fill_value(self): - return self.values.fill_value - - @fill_value.setter - def fill_value(self, v): - self.values.fill_value = v - - @property - def sp_index(self): - return self.values.sp_index - - @property - def sp_values(self): - return self.values.sp_values - - @property - def npoints(self): - return self.values.npoints - - @classmethod - def from_array( - cls, arr, index=None, name=None, copy=False, fill_value=None, fastpath=False - ): - """Construct SparseSeries from array. - - .. deprecated:: 0.23.0 - Use the pd.SparseSeries(..) constructor instead. - """ - warnings.warn( - "'from_array' is deprecated and will be removed in a " - "future version. Please use the pd.SparseSeries(..) " - "constructor instead.", - FutureWarning, - stacklevel=2, - ) - return cls( - arr, - index=index, - name=name, - copy=copy, - fill_value=fill_value, - fastpath=fastpath, - ) - - @property - def _constructor(self): - return SparseSeries - - @property - def _constructor_expanddim(self): - from pandas.core.sparse.api import SparseDataFrame - - return SparseDataFrame - - @property - def kind(self): - if isinstance(self.sp_index, BlockIndex): - return "block" - elif isinstance(self.sp_index, IntIndex): - return "integer" - - def as_sparse_array(self, kind=None, fill_value=None, copy=False): - """ return my self as a sparse array, do not copy by default """ - - if fill_value is None: - fill_value = self.fill_value - if kind is None: - kind = self.kind - return SparseArray( - self.values, - sparse_index=self.sp_index, - fill_value=fill_value, - kind=kind, - copy=copy, - ) - - def __repr__(self): - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", "Sparse") - series_rep = Series.__repr__(self) - rep = "{series}\n{index!r}".format(series=series_rep, index=self.sp_index) - return rep - - def _reduce( - self, op, name, axis=0, skipna=True, numeric_only=None, filter_type=None, **kwds - ): - """ perform a reduction operation """ - return op(self.array.to_dense(), skipna=skipna, **kwds) - - def __getstate__(self): - # pickling - return dict( - _typ=self._typ, - _subtyp=self._subtyp, - _data=self._data, - fill_value=self.fill_value, - name=self.name, - ) - - def _unpickle_series_compat(self, state): - - nd_state, own_state = state - - # recreate the ndarray - data = np.empty(nd_state[1], dtype=nd_state[2]) - np.ndarray.__setstate__(data, nd_state) - - index, fill_value, sp_index = own_state[:3] - name = None - if len(own_state) > 3: - name = own_state[3] - - # create a sparse array - if not isinstance(data, SparseArray): - data = SparseArray( - data, sparse_index=sp_index, fill_value=fill_value, copy=False - ) - - # recreate - data = SingleBlockManager(data, index, fastpath=True) - generic.NDFrame.__init__(self, data) - - self._set_axis(0, index) - self.name = name - - def _set_subtyp(self, is_all_dates): - if is_all_dates: - object.__setattr__(self, "_subtyp", "sparse_time_series") - else: - object.__setattr__(self, "_subtyp", "sparse_series") - - # ---------------------------------------------------------------------- - # Indexing Methods - - def _ixs(self, i: int, axis: int = 0): - """ - Return the i-th value or values in the SparseSeries by location - - Parameters - ---------- - i : int - axis : int - default 0, ignored - - Returns - ------- - value : scalar (int) or Series (slice, sequence) - """ - assert is_integer(i), i - # equiv: self._get_val_at(i) since we have an integer - return self.values[i] - - def _get_val_at(self, loc): - """ forward to the array """ - return self.values._get_val_at(loc) - - def __getitem__(self, key): - # TODO: Document difference from Series.__getitem__, deprecate, - # and remove! - if is_integer(key) and key not in self.index: - return self._get_val_at(key) - else: - return super().__getitem__(key) - - def _get_value(self, label, takeable=False): - """ - Retrieve single value at passed index label - - Please use .at[] or .iat[] accessors. - - Parameters - ---------- - index : label - takeable : interpret the index as indexers, default False - - Returns - ------- - value : scalar value - """ - loc = label if takeable is True else self.index.get_loc(label) - return self._get_val_at(loc) - - def _get_values(self, indexer): - try: - return self._constructor( - self._data.get_slice(indexer), fastpath=True - ).__finalize__(self) - except Exception: - return self[indexer] - - def _set_with_engine(self, key, value): - return self._set_value(key, value) - - def _set_value(self, label, value, takeable=False): - """ - Quickly set single value at passed label. If label is not contained, a - new object is created with the label placed at the end of the result - index - - .. deprecated:: 0.21.0 - - Please use .at[] or .iat[] accessors. - - Parameters - ---------- - label : object - Partial indexing with MultiIndex not allowed - value : object - Scalar value - takeable : interpret the index as indexers, default False - - Notes - ----- - This method *always* returns a new object. It is not particularly - efficient but is provided for API compatibility with Series - - Returns - ------- - series : SparseSeries - """ - values = self.to_dense() - - # if the label doesn't exist, we will create a new object here - # and possibly change the index - new_values = values._set_value(label, value, takeable=takeable) - if new_values is not None: - values = new_values - new_index = values.index - values = SparseArray(values, fill_value=self.fill_value, kind=self.kind) - self._data = SingleBlockManager(values, new_index) - self._index = new_index - - def _set_values(self, key, value): - - # this might be inefficient as we have to recreate the sparse array - # rather than setting individual elements, but have to convert - # the passed slice/boolean that's in dense space into a sparse indexer - # not sure how to do that! - if isinstance(key, Series): - key = key.values - - values = self.values.to_dense() - values[key] = libindex.convert_scalar(values, value) - values = SparseArray(values, fill_value=self.fill_value, kind=self.kind) - self._data = SingleBlockManager(values, self.index) - - # ---------------------------------------------------------------------- - # Unsorted - - def abs(self): - """ - Return an object with absolute value taken. Only applicable to objects - that are all numeric - - Returns - ------- - abs: same type as caller - """ - return self._constructor(np.abs(self.values), index=self.index).__finalize__( - self - ) - - def get(self, label, default=None): - """ - Returns value occupying requested label, default to specified - missing value if not present. Analogous to dict.get - - Parameters - ---------- - label : object - Label value looking for - default : object, optional - Value to return if label not in index - - Returns - ------- - y : scalar - """ - if label in self.index: - loc = self.index.get_loc(label) - return self._get_val_at(loc) - else: - return default - - def to_dense(self): - """ - Convert SparseSeries to a Series. - - Returns - ------- - s : Series - """ - return Series(self.values.to_dense(), index=self.index, name=self.name) - - @property - def density(self): - return self.values.density - - def copy(self, deep=True): - """ - Make a copy of the SparseSeries. Only the actual sparse values need to - be copied - """ - # TODO: https://github.com/pandas-dev/pandas/issues/22314 - # We skip the block manager till that is resolved. - new_data = self.values - if deep: - new_data = new_data.copy() - return self._constructor( - new_data, - sparse_index=self.sp_index, - fill_value=self.fill_value, - index=self.index.copy(), - name=self.name, - ).__finalize__(self) - - @Substitution(**_shared_doc_kwargs) - @Appender(generic.NDFrame.reindex.__doc__) - def reindex(self, index=None, method=None, copy=True, limit=None, **kwargs): - # TODO: remove? - return super().reindex( - index=index, method=method, copy=copy, limit=limit, **kwargs - ) - - def sparse_reindex(self, new_index): - """ - Conform sparse values to new SparseIndex - - Parameters - ---------- - new_index : {BlockIndex, IntIndex} - - Returns - ------- - reindexed : SparseSeries - """ - if not isinstance(new_index, splib.SparseIndex): - raise TypeError("new index must be a SparseIndex") - values = self.values - values = values.sp_index.to_int_index().reindex( - values.sp_values.astype("float64"), values.fill_value, new_index - ) - values = SparseArray( - values, sparse_index=new_index, fill_value=self.values.fill_value - ) - return self._constructor(values, index=self.index).__finalize__(self) - - def cumsum(self, axis=0, *args, **kwargs): - """ - Cumulative sum of non-NA/null values. - - When performing the cumulative summation, any non-NA/null values will - be skipped. The resulting SparseSeries will preserve the locations of - NaN values, but the fill value will be `np.nan` regardless. - - Parameters - ---------- - axis : {0} - - Returns - ------- - cumsum : SparseSeries - """ - nv.validate_cumsum(args, kwargs) - # Validate axis - if axis is not None: - self._get_axis_number(axis) - - new_array = self.values.cumsum() - - return self._constructor( - new_array, index=self.index, sparse_index=new_array.sp_index - ).__finalize__(self) - - # TODO: SparseSeries.isna is Sparse, while Series.isna is dense - @Appender(generic._shared_docs["isna"] % _shared_doc_kwargs) - def isna(self): - arr = SparseArray( - isna(self.values.sp_values), - sparse_index=self.values.sp_index, - fill_value=isna(self.fill_value), - ) - return self._constructor(arr, index=self.index).__finalize__(self) - - isnull = isna - - @Appender(generic._shared_docs["notna"] % _shared_doc_kwargs) - def notna(self): - arr = SparseArray( - notna(self.values.sp_values), - sparse_index=self.values.sp_index, - fill_value=notna(self.fill_value), - ) - return self._constructor(arr, index=self.index).__finalize__(self) - - notnull = notna - - def dropna(self, axis=0, inplace=False, **kwargs): - """ - Analogous to Series.dropna. If fill_value=NaN, returns a dense Series - """ - # TODO: make more efficient - # Validate axis - self._get_axis_number(axis or 0) - dense_valid = self.to_dense().dropna() - if inplace: - raise NotImplementedError( - "Cannot perform inplace dropna operations on a SparseSeries" - ) - if isna(self.fill_value): - return dense_valid - else: - dense_valid = dense_valid[dense_valid != self.fill_value] - return dense_valid.to_sparse(fill_value=self.fill_value) - - def combine_first(self, other): - """ - Combine Series values, choosing the calling Series's values - first. Result index will be the union of the two indexes - - Parameters - ---------- - other : Series - - Returns - ------- - y : Series - """ - if isinstance(other, SparseSeries): - other = other.to_dense() - - dense_combined = self.to_dense().combine_first(other) - return dense_combined.to_sparse(fill_value=self.fill_value) - - @Appender(SparseAccessor.to_coo.__doc__) - def to_coo(self, row_levels=(0,), column_levels=(1,), sort_labels=False): - A, rows, columns = _sparse_series_to_coo( - self, row_levels, column_levels, sort_labels=sort_labels - ) - return A, rows, columns - - @classmethod - @Appender(SparseAccessor.from_coo.__doc__) - def from_coo(cls, A, dense_index=False): - return _coo_to_sparse_series(A, dense_index=dense_index) - - -# overwrite series methods with unaccelerated Sparse-specific versions -ops.add_flex_arithmetic_methods(SparseSeries) -ops.add_special_arithmetic_methods(SparseSeries) diff --git a/pandas/io/packers.py b/pandas/io/packers.py index ad47ba23b9221..ecf7c77c172d1 100644 --- a/pandas/io/packers.py +++ b/pandas/io/packers.py @@ -85,7 +85,6 @@ from pandas.core.arrays.sparse import BlockIndex, IntIndex from pandas.core.generic import NDFrame from pandas.core.internals import BlockManager, _safe_reshape, make_block -from pandas.core.sparse.api import SparseDataFrame, SparseSeries from pandas.io.common import _stringify_path, get_filepath_or_buffer from pandas.io.msgpack import ExtType, Packer as _Packer, Unpacker as _Unpacker @@ -469,62 +468,37 @@ def encode(obj): } elif isinstance(obj, Series): - if isinstance(obj, SparseSeries): - raise NotImplementedError("msgpack sparse series is not implemented") - # d = {'typ': 'sparse_series', - # 'klass': obj.__class__.__name__, - # 'dtype': obj.dtype.name, - # 'index': obj.index, - # 'sp_index': obj.sp_index, - # 'sp_values': convert(obj.sp_values), - # 'compress': compressor} - # for f in ['name', 'fill_value', 'kind']: - # d[f] = getattr(obj, f, None) - # return d - else: - return { - "typ": "series", - "klass": obj.__class__.__name__, - "name": getattr(obj, "name", None), - "index": obj.index, - "dtype": obj.dtype.name, - "data": convert(obj.values), - "compress": compressor, - } + return { + "typ": "series", + "klass": obj.__class__.__name__, + "name": getattr(obj, "name", None), + "index": obj.index, + "dtype": obj.dtype.name, + "data": convert(obj.values), + "compress": compressor, + } elif issubclass(tobj, NDFrame): - if isinstance(obj, SparseDataFrame): - raise NotImplementedError("msgpack sparse frame is not implemented") - # d = {'typ': 'sparse_dataframe', - # 'klass': obj.__class__.__name__, - # 'columns': obj.columns} - # for f in ['default_fill_value', 'default_kind']: - # d[f] = getattr(obj, f, None) - # d['data'] = dict([(name, ss) - # for name, ss in obj.items()]) - # return d - else: - - data = obj._data - if not data.is_consolidated(): - data = data.consolidate() + data = obj._data + if not data.is_consolidated(): + data = data.consolidate() - # the block manager - return { - "typ": "block_manager", - "klass": obj.__class__.__name__, - "axes": data.axes, - "blocks": [ - { - "locs": b.mgr_locs.as_array, - "values": convert(b.values), - "shape": b.values.shape, - "dtype": b.dtype.name, - "klass": b.__class__.__name__, - "compress": compressor, - } - for b in data.blocks - ], - } + # the block manager + return { + "typ": "block_manager", + "klass": obj.__class__.__name__, + "axes": data.axes, + "blocks": [ + { + "locs": b.mgr_locs.as_array, + "values": convert(b.values), + "shape": b.values.shape, + "dtype": b.dtype.name, + "klass": b.__class__.__name__, + "compress": compressor, + } + for b in data.blocks + ], + } elif ( isinstance(obj, (datetime, date, np.datetime64, timedelta, np.timedelta64)) @@ -708,18 +682,6 @@ def create_block(b): return timedelta(*obj["data"]) elif typ == "timedelta64": return np.timedelta64(int(obj["data"])) - # elif typ == 'sparse_series': - # dtype = dtype_for(obj['dtype']) - # return SparseSeries( - # unconvert(obj['sp_values'], dtype, obj['compress']), - # sparse_index=obj['sp_index'], index=obj['index'], - # fill_value=obj['fill_value'], kind=obj['kind'], name=obj['name']) - # elif typ == 'sparse_dataframe': - # return SparseDataFrame( - # obj['data'], columns=obj['columns'], - # default_fill_value=obj['default_fill_value'], - # default_kind=obj['default_kind'] - # ) elif typ == "block_index": return globals()[obj["klass"]](obj["length"], obj["blocs"], obj["blengths"]) elif typ == "int_index": diff --git a/pandas/io/pytables.py b/pandas/io/pytables.py index 1ff3400323e54..feb6f97abb393 100644 --- a/pandas/io/pytables.py +++ b/pandas/io/pytables.py @@ -40,8 +40,6 @@ MultiIndex, PeriodIndex, Series, - SparseDataFrame, - SparseSeries, TimedeltaIndex, concat, isna, @@ -173,12 +171,7 @@ class DuplicateWarning(Warning): """ # map object types -_TYPE_MAP = { - Series: "series", - SparseSeries: "sparse_series", - DataFrame: "frame", - SparseDataFrame: "sparse_frame", -} +_TYPE_MAP = {Series: "series", DataFrame: "frame"} # storer class map _STORER_MAP = { @@ -186,9 +179,7 @@ class DuplicateWarning(Warning): "DataFrame": "LegacyFrameFixed", "DataMatrix": "LegacyFrameFixed", "series": "SeriesFixed", - "sparse_series": "SparseSeriesFixed", "frame": "FrameFixed", - "sparse_frame": "SparseFrameFixed", } # table class map @@ -3078,83 +3069,6 @@ def write(self, obj, **kwargs): self.attrs.name = obj.name -class SparseFixed(GenericFixed): - def validate_read(self, kwargs): - """ - we don't support start, stop kwds in Sparse - """ - kwargs = super().validate_read(kwargs) - if "start" in kwargs or "stop" in kwargs: - raise NotImplementedError( - "start and/or stop are not supported in fixed Sparse reading" - ) - return kwargs - - -class SparseSeriesFixed(SparseFixed): - pandas_kind = "sparse_series" - attributes = ["name", "fill_value", "kind"] - - def read(self, **kwargs): - kwargs = self.validate_read(kwargs) - index = self.read_index("index") - sp_values = self.read_array("sp_values") - sp_index = self.read_index("sp_index") - return SparseSeries( - sp_values, - index=index, - sparse_index=sp_index, - kind=self.kind or "block", - fill_value=self.fill_value, - name=self.name, - ) - - def write(self, obj, **kwargs): - super().write(obj, **kwargs) - self.write_index("index", obj.index) - self.write_index("sp_index", obj.sp_index) - self.write_array("sp_values", obj.sp_values) - self.attrs.name = obj.name - self.attrs.fill_value = obj.fill_value - self.attrs.kind = obj.kind - - -class SparseFrameFixed(SparseFixed): - pandas_kind = "sparse_frame" - attributes = ["default_kind", "default_fill_value"] - - def read(self, **kwargs): - kwargs = self.validate_read(kwargs) - columns = self.read_index("columns") - sdict = {} - for c in columns: - key = "sparse_series_{columns}".format(columns=c) - s = SparseSeriesFixed(self.parent, getattr(self.group, key)) - s.infer_axes() - sdict[c] = s.read() - return SparseDataFrame( - sdict, - columns=columns, - default_kind=self.default_kind, - default_fill_value=self.default_fill_value, - ) - - def write(self, obj, **kwargs): - """ write it as a collection of individual sparse series """ - super().write(obj, **kwargs) - for name, ss in obj.items(): - key = "sparse_series_{name}".format(name=name) - if key not in self.group._v_children: - node = self._handle.create_group(self.group, key) - else: - node = getattr(self.group, key) - s = SparseSeriesFixed(self.parent, node) - s.write(ss) - self.attrs.default_fill_value = obj.default_fill_value - self.attrs.default_kind = obj.default_kind - self.write_index("columns", obj.columns) - - class BlockManagerFixed(GenericFixed): attributes = ["ndim", "nblocks"] is_shape_reversed = False diff --git a/pandas/tests/arrays/sparse/test_arithmetics.py b/pandas/tests/arrays/sparse/test_arithmetics.py index cb5b437c962f9..9d5f0e365eefd 100644 --- a/pandas/tests/arrays/sparse/test_arithmetics.py +++ b/pandas/tests/arrays/sparse/test_arithmetics.py @@ -391,48 +391,6 @@ def test_mixed_array_comparison(self, kind): self._check_comparison_ops(a, b, values, rvalues) -class TestSparseSeriesArithmetic(TestSparseArrayArithmetics): - - _base = pd.Series - _klass = pd.SparseSeries - - def _assert(self, a, b): - tm.assert_series_equal(a, b) - - def test_alignment(self, mix, all_arithmetic_functions): - op = all_arithmetic_functions - - da = pd.Series(np.arange(4)) - db = pd.Series(np.arange(4), index=[1, 2, 3, 4]) - - sa = pd.SparseSeries(np.arange(4), dtype=np.int64, fill_value=0) - sb = pd.SparseSeries( - np.arange(4), index=[1, 2, 3, 4], dtype=np.int64, fill_value=0 - ) - self._check_numeric_ops(sa, sb, da, db, mix, op) - - sa = pd.SparseSeries(np.arange(4), dtype=np.int64, fill_value=np.nan) - sb = pd.SparseSeries( - np.arange(4), index=[1, 2, 3, 4], dtype=np.int64, fill_value=np.nan - ) - self._check_numeric_ops(sa, sb, da, db, mix, op) - - da = pd.Series(np.arange(4)) - db = pd.Series(np.arange(4), index=[10, 11, 12, 13]) - - sa = pd.SparseSeries(np.arange(4), dtype=np.int64, fill_value=0) - sb = pd.SparseSeries( - np.arange(4), index=[10, 11, 12, 13], dtype=np.int64, fill_value=0 - ) - self._check_numeric_ops(sa, sb, da, db, mix, op) - - sa = pd.SparseSeries(np.arange(4), dtype=np.int64, fill_value=np.nan) - sb = pd.SparseSeries( - np.arange(4), index=[10, 11, 12, 13], dtype=np.int64, fill_value=np.nan - ) - self._check_numeric_ops(sa, sb, da, db, mix, op) - - @pytest.mark.parametrize("op", [operator.eq, operator.add]) def test_with_list(op): arr = pd.SparseArray([0, 1], fill_value=0) diff --git a/pandas/tests/arrays/sparse/test_array.py b/pandas/tests/arrays/sparse/test_array.py index b94e2a16d217a..c26aefa123f20 100644 --- a/pandas/tests/arrays/sparse/test_array.py +++ b/pandas/tests/arrays/sparse/test_array.py @@ -10,7 +10,7 @@ import pandas as pd from pandas import isna -from pandas.core.sparse.api import SparseArray, SparseDtype, SparseSeries +from pandas.core.sparse.api import SparseArray, SparseDtype import pandas.util.testing as tm from pandas.util.testing import assert_almost_equal @@ -221,36 +221,6 @@ def test_scalar_with_index_infer_dtype(self, scalar, dtype): assert arr.dtype == dtype assert exp.dtype == dtype - @pytest.mark.parametrize("fill", [1, np.nan, 0]) - @pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") - def test_sparse_series_round_trip(self, kind, fill): - # see gh-13999 - arr = SparseArray([np.nan, 1, np.nan, 2, 3], kind=kind, fill_value=fill) - res = SparseArray(SparseSeries(arr)) - tm.assert_sp_array_equal(arr, res) - - arr = SparseArray( - [0, 0, 0, 1, 1, 2], dtype=np.int64, kind=kind, fill_value=fill - ) - res = SparseArray(SparseSeries(arr), dtype=np.int64) - tm.assert_sp_array_equal(arr, res) - - res = SparseArray(SparseSeries(arr)) - tm.assert_sp_array_equal(arr, res) - - @pytest.mark.parametrize("fill", [True, False, np.nan]) - @pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") - def test_sparse_series_round_trip2(self, kind, fill): - # see gh-13999 - arr = SparseArray( - [True, False, True, True], dtype=np.bool, kind=kind, fill_value=fill - ) - res = SparseArray(SparseSeries(arr)) - tm.assert_sp_array_equal(arr, res) - - res = SparseArray(SparseSeries(arr)) - tm.assert_sp_array_equal(arr, res) - def test_get_item(self): assert np.isnan(self.arr[1]) diff --git a/pandas/tests/dtypes/test_generic.py b/pandas/tests/dtypes/test_generic.py index b42822a03ebcd..a4c7965bf7554 100644 --- a/pandas/tests/dtypes/test_generic.py +++ b/pandas/tests/dtypes/test_generic.py @@ -40,9 +40,7 @@ def test_abc_types(self): assert isinstance(pd.Int64Index([1, 2, 3]), gt.ABCIndexClass) assert isinstance(pd.Series([1, 2, 3]), gt.ABCSeries) assert isinstance(self.df, gt.ABCDataFrame) - assert isinstance(self.sparse_series, gt.ABCSparseSeries) assert isinstance(self.sparse_array, gt.ABCSparseArray) - assert isinstance(self.sparse_frame, gt.ABCSparseDataFrame) assert isinstance(self.categorical, gt.ABCCategorical) assert isinstance(pd.Period("2012", freq="A-DEC"), gt.ABCPeriod) diff --git a/pandas/tests/sparse/frame/__init__.py b/pandas/tests/sparse/frame/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/pandas/tests/sparse/frame/conftest.py b/pandas/tests/sparse/frame/conftest.py deleted file mode 100644 index 989b58419c2cd..0000000000000 --- a/pandas/tests/sparse/frame/conftest.py +++ /dev/null @@ -1,120 +0,0 @@ -import numpy as np -import pytest - -from pandas import DataFrame, SparseArray, SparseDataFrame, bdate_range - -data = { - "A": [np.nan, np.nan, np.nan, 0, 1, 2, 3, 4, 5, 6], - "B": [0, 1, 2, np.nan, np.nan, np.nan, 3, 4, 5, 6], - "C": np.arange(10, dtype=np.float64), - "D": [0, 1, 2, 3, 4, 5, np.nan, np.nan, np.nan, np.nan], -} -dates = bdate_range("1/1/2011", periods=10) - - -# fixture names must be compatible with the tests in -# tests/frame/test_api.SharedWithSparse - - -@pytest.fixture -def float_frame_dense(): - """ - Fixture for dense DataFrame of floats with DatetimeIndex - - Columns are ['A', 'B', 'C', 'D']; some entries are missing - """ - return DataFrame(data, index=dates) - - -@pytest.fixture -def float_frame(): - """ - Fixture for sparse DataFrame of floats with DatetimeIndex - - Columns are ['A', 'B', 'C', 'D']; some entries are missing - """ - # default_kind='block' is the default - return SparseDataFrame(data, index=dates, default_kind="block") - - -@pytest.fixture -def float_frame_int_kind(): - """ - Fixture for sparse DataFrame of floats with DatetimeIndex - - Columns are ['A', 'B', 'C', 'D'] and default_kind='integer'. - Some entries are missing. - """ - return SparseDataFrame(data, index=dates, default_kind="integer") - - -@pytest.fixture -def float_string_frame(): - """ - Fixture for sparse DataFrame of floats and strings with DatetimeIndex - - Columns are ['A', 'B', 'C', 'D', 'foo']; some entries are missing - """ - sdf = SparseDataFrame(data, index=dates) - sdf["foo"] = SparseArray(["bar"] * len(dates)) - return sdf - - -@pytest.fixture -def float_frame_fill0_dense(): - """ - Fixture for dense DataFrame of floats with DatetimeIndex - - Columns are ['A', 'B', 'C', 'D']; missing entries have been filled with 0 - """ - values = SparseDataFrame(data).values - values[np.isnan(values)] = 0 - return DataFrame(values, columns=["A", "B", "C", "D"], index=dates) - - -@pytest.fixture -def float_frame_fill0(): - """ - Fixture for sparse DataFrame of floats with DatetimeIndex - - Columns are ['A', 'B', 'C', 'D']; missing entries have been filled with 0 - """ - values = SparseDataFrame(data).values - values[np.isnan(values)] = 0 - return SparseDataFrame( - values, columns=["A", "B", "C", "D"], default_fill_value=0, index=dates - ) - - -@pytest.fixture -def float_frame_fill2_dense(): - """ - Fixture for dense DataFrame of floats with DatetimeIndex - - Columns are ['A', 'B', 'C', 'D']; missing entries have been filled with 2 - """ - values = SparseDataFrame(data).values - values[np.isnan(values)] = 2 - return DataFrame(values, columns=["A", "B", "C", "D"], index=dates) - - -@pytest.fixture -def float_frame_fill2(): - """ - Fixture for sparse DataFrame of floats with DatetimeIndex - - Columns are ['A', 'B', 'C', 'D']; missing entries have been filled with 2 - """ - values = SparseDataFrame(data).values - values[np.isnan(values)] = 2 - return SparseDataFrame( - values, columns=["A", "B", "C", "D"], default_fill_value=2, index=dates - ) - - -@pytest.fixture -def empty_frame(): - """ - Fixture for empty SparseDataFrame - """ - return SparseDataFrame() diff --git a/pandas/tests/sparse/frame/test_analytics.py b/pandas/tests/sparse/frame/test_analytics.py deleted file mode 100644 index fae879b3d33b5..0000000000000 --- a/pandas/tests/sparse/frame/test_analytics.py +++ /dev/null @@ -1,41 +0,0 @@ -import numpy as np -import pytest - -from pandas import DataFrame, SparseDataFrame, SparseSeries -from pandas.util import testing as tm - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.xfail(reason="Wrong SparseBlock initialization (GH#17386)") -def test_quantile(): - # GH 17386 - data = [[1, 1], [2, 10], [3, 100], [np.nan, np.nan]] - q = 0.1 - - sparse_df = SparseDataFrame(data) - result = sparse_df.quantile(q) - - dense_df = DataFrame(data) - dense_expected = dense_df.quantile(q) - sparse_expected = SparseSeries(dense_expected) - - tm.assert_series_equal(result, dense_expected) - tm.assert_sp_series_equal(result, sparse_expected) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.xfail(reason="Wrong SparseBlock initialization (GH#17386)") -def test_quantile_multi(): - # GH 17386 - data = [[1, 1], [2, 10], [3, 100], [np.nan, np.nan]] - q = [0.1, 0.5] - - sparse_df = SparseDataFrame(data) - result = sparse_df.quantile(q) - - dense_df = DataFrame(data) - dense_expected = dense_df.quantile(q) - sparse_expected = SparseDataFrame(dense_expected) - - tm.assert_frame_equal(result, dense_expected) - tm.assert_sp_frame_equal(result, sparse_expected) diff --git a/pandas/tests/sparse/frame/test_apply.py b/pandas/tests/sparse/frame/test_apply.py deleted file mode 100644 index d8158db32d8f0..0000000000000 --- a/pandas/tests/sparse/frame/test_apply.py +++ /dev/null @@ -1,117 +0,0 @@ -import numpy as np -import pytest - -from pandas import DataFrame, Series, SparseDataFrame, bdate_range -from pandas.core import nanops -from pandas.core.sparse.api import SparseDtype -from pandas.util import testing as tm - - -@pytest.fixture -def dates(): - return bdate_range("1/1/2011", periods=10) - - -@pytest.fixture -def empty(): - return SparseDataFrame() - - -@pytest.fixture -def frame(dates): - data = { - "A": [np.nan, np.nan, np.nan, 0, 1, 2, 3, 4, 5, 6], - "B": [0, 1, 2, np.nan, np.nan, np.nan, 3, 4, 5, 6], - "C": np.arange(10, dtype=np.float64), - "D": [0, 1, 2, 3, 4, 5, np.nan, np.nan, np.nan, np.nan], - } - - return SparseDataFrame(data, index=dates) - - -@pytest.fixture -def fill_frame(frame): - values = frame.values.copy() - values[np.isnan(values)] = 2 - - return SparseDataFrame( - values, columns=["A", "B", "C", "D"], default_fill_value=2, index=frame.index - ) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") -def test_apply(frame): - applied = frame.apply(np.sqrt) - assert isinstance(applied, SparseDataFrame) - tm.assert_almost_equal(applied.values, np.sqrt(frame.values)) - - # agg / broadcast - # two FutureWarnings, so we can't check stacklevel properly. - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): - broadcasted = frame.apply(np.sum, broadcast=True) - assert isinstance(broadcasted, SparseDataFrame) - - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): - exp = frame.to_dense().apply(np.sum, broadcast=True) - tm.assert_frame_equal(broadcasted.to_dense(), exp) - - applied = frame.apply(np.sum) - tm.assert_series_equal(applied, frame.to_dense().apply(nanops.nansum).to_sparse()) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -def test_apply_fill(fill_frame): - applied = fill_frame.apply(np.sqrt) - assert applied["A"].fill_value == np.sqrt(2) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -def test_apply_empty(empty): - assert empty.apply(np.sqrt) is empty - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:DataFrame.to_sparse:FutureWarning") -def test_apply_nonuq(): - orig = DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], index=["a", "a", "c"]) - sparse = orig.to_sparse() - res = sparse.apply(lambda s: s[0], axis=1) - exp = orig.apply(lambda s: s[0], axis=1) - - # dtype must be kept - assert res.dtype == SparseDtype(np.int64) - - # ToDo: apply must return subclassed dtype - assert isinstance(res, Series) - tm.assert_series_equal(res.to_dense(), exp) - - # df.T breaks - sparse = orig.T.to_sparse() - res = sparse.apply(lambda s: s[0], axis=0) # noqa - exp = orig.T.apply(lambda s: s[0], axis=0) - - # TODO: no non-unique columns supported in sparse yet - # tm.assert_series_equal(res.to_dense(), exp) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -def test_applymap(frame): - # just test that it works - result = frame.applymap(lambda x: x * 2) - assert isinstance(result, SparseDataFrame) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -def test_apply_keep_sparse_dtype(): - # GH 23744 - sdf = SparseDataFrame( - np.array([[0, 1, 0], [0, 0, 0], [0, 0, 1]]), - columns=["b", "a", "c"], - default_fill_value=1, - ) - df = DataFrame(sdf) - - expected = sdf.apply(np.exp) - result = df.apply(np.exp) - tm.assert_frame_equal(expected, result) diff --git a/pandas/tests/sparse/frame/test_frame.py b/pandas/tests/sparse/frame/test_frame.py deleted file mode 100644 index e372e2563e682..0000000000000 --- a/pandas/tests/sparse/frame/test_frame.py +++ /dev/null @@ -1,1596 +0,0 @@ -import operator -from types import LambdaType - -import numpy as np -from numpy import nan -import pytest - -from pandas._libs.sparse import BlockIndex, IntIndex -from pandas.errors import PerformanceWarning - -import pandas as pd -from pandas import DataFrame, Series, bdate_range, compat -from pandas.core import ops -from pandas.core.indexes.datetimes import DatetimeIndex -from pandas.core.sparse import frame as spf -from pandas.core.sparse.api import ( - SparseArray, - SparseDataFrame, - SparseDtype, - SparseSeries, -) -from pandas.tests.frame.test_api import SharedWithSparse -from pandas.util import testing as tm - -from pandas.tseries.offsets import BDay - - -def test_deprecated(): - with tm.assert_produces_warning(FutureWarning): - pd.SparseDataFrame({"A": [1, 2]}) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:DataFrame.to_sparse:FutureWarning") -class TestSparseDataFrame(SharedWithSparse): - klass = SparseDataFrame - - # SharedWithSparse tests use generic, klass-agnostic assertion - _assert_frame_equal = staticmethod(tm.assert_sp_frame_equal) - _assert_series_equal = staticmethod(tm.assert_sp_series_equal) - - def test_iterrows(self, float_frame, float_string_frame): - # Same as parent, but we don't ensure the sparse kind is the same. - for k, v in float_frame.iterrows(): - exp = float_frame.loc[k] - tm.assert_sp_series_equal(v, exp, check_kind=False) - - for k, v in float_string_frame.iterrows(): - exp = float_string_frame.loc[k] - tm.assert_sp_series_equal(v, exp, check_kind=False) - - def test_itertuples(self, float_frame): - for i, tup in enumerate(float_frame.itertuples()): - s = self.klass._constructor_sliced(tup[1:]) - s.name = tup[0] - expected = float_frame.iloc[i, :].reset_index(drop=True) - tm.assert_sp_series_equal(s, expected, check_kind=False) - - def test_fill_value_when_combine_const(self): - # GH12723 - dat = np.array([0, 1, np.nan, 3, 4, 5], dtype="float") - df = SparseDataFrame({"foo": dat}, index=range(6)) - - exp = df.fillna(0).add(2) - res = df.add(2, fill_value=0) - tm.assert_sp_frame_equal(res, exp) - - def test_values(self, empty_frame, float_frame): - empty = empty_frame.values - assert empty.shape == (0, 0) - - no_cols = SparseDataFrame(index=np.arange(10)) - mat = no_cols.values - assert mat.shape == (10, 0) - - no_index = SparseDataFrame(columns=np.arange(10)) - mat = no_index.values - assert mat.shape == (0, 10) - - def test_copy(self, float_frame): - cp = float_frame.copy() - assert isinstance(cp, SparseDataFrame) - tm.assert_sp_frame_equal(cp, float_frame) - - # as of v0.15.0 - # this is now identical (but not is_a ) - assert cp.index.identical(float_frame.index) - - def test_constructor(self, float_frame, float_frame_int_kind, float_frame_fill0): - for col, series in float_frame.items(): - assert isinstance(series, SparseSeries) - - assert isinstance(float_frame_int_kind["A"].sp_index, IntIndex) - - # constructed zframe from matrix above - assert float_frame_fill0["A"].fill_value == 0 - # XXX: changed asarray - expected = pd.SparseArray( - [0, 0, 0, 0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], fill_value=0, kind="block" - ) - tm.assert_sp_array_equal(expected, float_frame_fill0["A"].values) - tm.assert_numpy_array_equal( - np.array([0.0, 0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0]), - float_frame_fill0["A"].to_dense().values, - ) - - # construct no data - sdf = SparseDataFrame(columns=np.arange(10), index=np.arange(10)) - for col, series in sdf.items(): - assert isinstance(series, SparseSeries) - - # construct from nested dict - data = {c: s.to_dict() for c, s in float_frame.items()} - - sdf = SparseDataFrame(data) - tm.assert_sp_frame_equal(sdf, float_frame) - - # TODO: test data is copied from inputs - - # init dict with different index - idx = float_frame.index[:5] - cons = SparseDataFrame( - float_frame, - index=idx, - columns=float_frame.columns, - default_fill_value=float_frame.default_fill_value, - default_kind=float_frame.default_kind, - copy=True, - ) - reindexed = float_frame.reindex(idx) - - tm.assert_sp_frame_equal(cons, reindexed, exact_indices=False) - - # assert level parameter breaks reindex - with pytest.raises(TypeError): - float_frame.reindex(idx, level=0) - - repr(float_frame) - - def test_constructor_fill_value_not_scalar_raises(self): - d = {"b": [2, 3], "a": [0, 1]} - fill_value = np.array(np.nan) - with pytest.raises(ValueError, match="must be a scalar"): - SparseDataFrame(data=d, default_fill_value=fill_value) - - def test_constructor_dict_order(self): - # GH19018 - # initialization ordering: by insertion order if python>= 3.6, else - # order by value - d = {"b": [2, 3], "a": [0, 1]} - frame = SparseDataFrame(data=d) - if compat.PY36: - expected = SparseDataFrame(data=d, columns=list("ba")) - else: - expected = SparseDataFrame(data=d, columns=list("ab")) - tm.assert_sp_frame_equal(frame, expected) - - def test_constructor_ndarray(self, float_frame): - # no index or columns - sp = SparseDataFrame(float_frame.values) - - # 1d - sp = SparseDataFrame( - float_frame["A"].values, index=float_frame.index, columns=["A"] - ) - tm.assert_sp_frame_equal(sp, float_frame.reindex(columns=["A"])) - - # raise on level argument - msg = "Reindex by level not supported for sparse" - with pytest.raises(TypeError, match=msg): - float_frame.reindex(columns=["A"], level=1) - - # wrong length index / columns - with pytest.raises(ValueError, match="^Index length"): - SparseDataFrame(float_frame.values, index=float_frame.index[:-1]) - - with pytest.raises(ValueError, match="^Column length"): - SparseDataFrame(float_frame.values, columns=float_frame.columns[:-1]) - - # GH 9272 - def test_constructor_empty(self): - sp = SparseDataFrame() - assert len(sp.index) == 0 - assert len(sp.columns) == 0 - - def test_constructor_dataframe(self, float_frame): - dense = float_frame.to_dense() - sp = SparseDataFrame(dense) - tm.assert_sp_frame_equal(sp, float_frame) - - def test_constructor_convert_index_once(self): - arr = np.array([1.5, 2.5, 3.5]) - sdf = SparseDataFrame(columns=range(4), index=arr) - assert sdf[0].index is sdf[1].index - - def test_constructor_from_series(self): - - # GH 2873 - x = Series(np.random.randn(10000), name="a") - x = x.to_sparse(fill_value=0) - assert isinstance(x, SparseSeries) - df = SparseDataFrame(x) - assert isinstance(df, SparseDataFrame) - - x = Series(np.random.randn(10000), name="a") - y = Series(np.random.randn(10000), name="b") - x2 = x.astype(float) - x2.loc[:9998] = np.NaN - # TODO: x_sparse is unused...fix - x_sparse = x2.to_sparse(fill_value=np.NaN) # noqa - - # Currently fails too with weird ufunc error - # df1 = SparseDataFrame([x_sparse, y]) - - y.loc[:9998] = 0 - # TODO: y_sparse is unsused...fix - y_sparse = y.to_sparse(fill_value=0) # noqa - # without sparse value raises error - # df2 = SparseDataFrame([x2_sparse, y]) - - def test_constructor_from_dense_series(self): - # GH 19393 - # series with name - x = Series(np.random.randn(10000), name="a") - result = SparseDataFrame(x) - expected = x.to_frame().to_sparse() - tm.assert_sp_frame_equal(result, expected) - - # series with no name - x = Series(np.random.randn(10000)) - result = SparseDataFrame(x) - expected = x.to_frame().to_sparse() - tm.assert_sp_frame_equal(result, expected) - - def test_constructor_from_unknown_type(self): - # GH 19393 - class Unknown: - pass - - with pytest.raises( - TypeError, - match=( - "SparseDataFrame called with unknown type " - '"Unknown" for data argument' - ), - ): - SparseDataFrame(Unknown()) - - def test_constructor_preserve_attr(self): - # GH 13866 - arr = pd.SparseArray([1, 0, 3, 0], dtype=np.int64, fill_value=0) - assert arr.dtype == SparseDtype(np.int64) - assert arr.fill_value == 0 - - df = pd.SparseDataFrame({"x": arr}) - assert df["x"].dtype == SparseDtype(np.int64) - assert df["x"].fill_value == 0 - - s = pd.SparseSeries(arr, name="x") - assert s.dtype == SparseDtype(np.int64) - assert s.fill_value == 0 - - df = pd.SparseDataFrame(s) - assert df["x"].dtype == SparseDtype(np.int64) - assert df["x"].fill_value == 0 - - df = pd.SparseDataFrame({"x": s}) - assert df["x"].dtype == SparseDtype(np.int64) - assert df["x"].fill_value == 0 - - def test_constructor_nan_dataframe(self): - # GH 10079 - trains = np.arange(100) - thresholds = [10, 20, 30, 40, 50, 60] - tuples = [(i, j) for i in trains for j in thresholds] - index = pd.MultiIndex.from_tuples(tuples, names=["trains", "thresholds"]) - matrix = np.empty((len(index), len(trains))) - matrix.fill(np.nan) - df = pd.DataFrame(matrix, index=index, columns=trains, dtype=float) - result = df.to_sparse() - expected = pd.SparseDataFrame(matrix, index=index, columns=trains, dtype=float) - tm.assert_sp_frame_equal(result, expected) - - def test_type_coercion_at_construction(self): - # GH 15682 - result = pd.SparseDataFrame( - {"a": [1, 0, 0], "b": [0, 1, 0], "c": [0, 0, 1]}, - dtype="uint8", - default_fill_value=0, - ) - expected = pd.SparseDataFrame( - { - "a": pd.SparseSeries([1, 0, 0], dtype="uint8"), - "b": pd.SparseSeries([0, 1, 0], dtype="uint8"), - "c": pd.SparseSeries([0, 0, 1], dtype="uint8"), - }, - default_fill_value=0, - ) - tm.assert_sp_frame_equal(result, expected) - - def test_default_dtype(self): - result = pd.SparseDataFrame(columns=list("ab"), index=range(2)) - expected = pd.SparseDataFrame( - [[np.nan, np.nan], [np.nan, np.nan]], columns=list("ab"), index=range(2) - ) - tm.assert_sp_frame_equal(result, expected) - - def test_nan_data_with_int_dtype_raises_error(self): - sdf = pd.SparseDataFrame( - [[np.nan, np.nan], [np.nan, np.nan]], columns=list("ab"), index=range(2) - ) - msg = "Cannot convert non-finite values" - with pytest.raises(ValueError, match=msg): - pd.SparseDataFrame(sdf, dtype=np.int64) - - def test_dtypes(self): - df = DataFrame(np.random.randn(10000, 4)) - df.loc[:9998] = np.nan - sdf = df.to_sparse() - result = sdf.dtypes - expected = Series(["Sparse[float64, nan]"] * 4) - tm.assert_series_equal(result, expected) - - def test_shape( - self, float_frame, float_frame_int_kind, float_frame_fill0, float_frame_fill2 - ): - # see gh-10452 - assert float_frame.shape == (10, 4) - assert float_frame_int_kind.shape == (10, 4) - assert float_frame_fill0.shape == (10, 4) - assert float_frame_fill2.shape == (10, 4) - - def test_str(self): - df = DataFrame(np.random.randn(10000, 4)) - df.loc[:9998] = np.nan - - sdf = df.to_sparse() - str(sdf) - - def test_array_interface(self, float_frame): - res = np.sqrt(float_frame) - dres = np.sqrt(float_frame.to_dense()) - tm.assert_frame_equal(res.to_dense(), dres) - - def test_pickle( - self, - float_frame, - float_frame_int_kind, - float_frame_dense, - float_frame_fill0, - float_frame_fill0_dense, - float_frame_fill2, - float_frame_fill2_dense, - ): - def _test_roundtrip(frame, orig): - result = tm.round_trip_pickle(frame) - tm.assert_sp_frame_equal(frame, result) - tm.assert_frame_equal(result.to_dense(), orig, check_dtype=False) - - _test_roundtrip(SparseDataFrame(), DataFrame()) - _test_roundtrip(float_frame, float_frame_dense) - _test_roundtrip(float_frame_int_kind, float_frame_dense) - _test_roundtrip(float_frame_fill0, float_frame_fill0_dense) - _test_roundtrip(float_frame_fill2, float_frame_fill2_dense) - - def test_dense_to_sparse(self): - df = DataFrame({"A": [nan, nan, nan, 1, 2], "B": [1, 2, nan, nan, nan]}) - sdf = df.to_sparse() - assert isinstance(sdf, SparseDataFrame) - assert np.isnan(sdf.default_fill_value) - assert isinstance(sdf["A"].sp_index, BlockIndex) - tm.assert_frame_equal(sdf.to_dense(), df) - - sdf = df.to_sparse(kind="integer") - assert isinstance(sdf["A"].sp_index, IntIndex) - - df = DataFrame({"A": [0, 0, 0, 1, 2], "B": [1, 2, 0, 0, 0]}, dtype=float) - sdf = df.to_sparse(fill_value=0) - assert sdf.default_fill_value == 0 - tm.assert_frame_equal(sdf.to_dense(), df) - - def test_deprecated_dense_to_sparse(self): - # GH 26557 - # Deprecated 0.25.0 - - df = pd.DataFrame({"A": [1, np.nan, 3]}) - sparse_df = pd.SparseDataFrame({"A": [1, np.nan, 3]}) - - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): - result = df.to_sparse() - tm.assert_frame_equal(result, sparse_df) - - def test_density(self): - df = SparseSeries([nan, nan, nan, 0, 1, 2, 3, 4, 5, 6]) - assert df.density == 0.7 - - df = SparseDataFrame( - { - "A": [nan, nan, nan, 0, 1, 2, 3, 4, 5, 6], - "B": [0, 1, 2, nan, nan, nan, 3, 4, 5, 6], - "C": np.arange(10), - "D": [0, 1, 2, 3, 4, 5, nan, nan, nan, nan], - } - ) - - assert df.density == 0.75 - - def test_sparse_to_dense(self): - pass - - def test_sparse_series_ops(self, float_frame): - self._check_frame_ops(float_frame) - - def test_sparse_series_ops_i(self, float_frame_int_kind): - self._check_frame_ops(float_frame_int_kind) - - def test_sparse_series_ops_z(self, float_frame_fill0): - self._check_frame_ops(float_frame_fill0) - - def test_sparse_series_ops_fill(self, float_frame_fill2): - self._check_frame_ops(float_frame_fill2) - - def _check_frame_ops(self, frame): - def _compare_to_dense(a, b, da, db, op): - sparse_result = op(a, b) - dense_result = op(da, db) - - # catch lambdas but not non-lambdas e.g. operator.add - if op in [operator.floordiv, ops.rfloordiv] or isinstance(op, LambdaType): - # GH#27231 Series sets 1//0 to np.inf, which SparseArray - # does not do (yet) - mask = np.isinf(dense_result) & ~np.isinf(sparse_result.to_dense()) - dense_result[mask] = np.nan - - fill = sparse_result.default_fill_value - dense_result = dense_result.to_sparse(fill_value=fill) - tm.assert_sp_frame_equal(sparse_result, dense_result, exact_indices=False) - - if isinstance(a, DataFrame) and isinstance(db, DataFrame): - mixed_result = op(a, db) - assert isinstance(mixed_result, SparseDataFrame) - tm.assert_sp_frame_equal( - mixed_result, sparse_result, exact_indices=False - ) - - opnames = ["add", "sub", "mul", "truediv", "floordiv"] - - fidx = frame.index - - # time series operations - - series = [ - frame["A"], - frame["B"], - frame["C"], - frame["D"], - frame["A"].reindex(fidx[:7]), - frame["A"].reindex(fidx[::2]), - SparseSeries([], index=[]), - ] - - for op in opnames: - _compare_to_dense( - frame, - frame[::2], - frame.to_dense(), - frame[::2].to_dense(), - getattr(operator, op), - ) - - # 2304, no auto-broadcasting - for i, s in enumerate(series): - f = lambda a, b: getattr(a, op)(b, axis="index") - _compare_to_dense(frame, s, frame.to_dense(), s.to_dense(), f) - - # FIXME: dont leave commented-out - # rops are not implemented - # _compare_to_dense(s, frame, s.to_dense(), - # frame.to_dense(), f) - - # cross-sectional operations - series = [ - frame.xs(fidx[0]), - frame.xs(fidx[3]), - frame.xs(fidx[5]), - frame.xs(fidx[7]), - frame.xs(fidx[5])[:2], - ] - - for name in opnames: - op = getattr(operator, name) - for s in series: - _compare_to_dense(frame, s, frame.to_dense(), s, op) - _compare_to_dense(s, frame, s, frame.to_dense(), op) - - # it works! - frame + frame.loc[:, ["A", "B"]] - - def test_op_corners(self, float_frame, empty_frame): - empty = empty_frame + empty_frame - assert empty.empty - - foo = float_frame + empty_frame - assert isinstance(foo.index, DatetimeIndex) - tm.assert_frame_equal(foo, float_frame * np.nan) - - foo = empty_frame + float_frame - tm.assert_frame_equal(foo, float_frame * np.nan) - - def test_scalar_ops(self): - pass - - def test_getitem(self): - # 1585 select multiple columns - sdf = SparseDataFrame(index=[0, 1, 2], columns=["a", "b", "c"]) - - result = sdf[["a", "b"]] - exp = sdf.reindex(columns=["a", "b"]) - tm.assert_sp_frame_equal(result, exp) - - with pytest.raises(KeyError, match=r"\['d'\] not in index"): - sdf[["a", "d"]] - - def test_iloc(self, float_frame): - - # GH 2227 - result = float_frame.iloc[:, 0] - assert isinstance(result, SparseSeries) - tm.assert_sp_series_equal(result, float_frame["A"]) - - # preserve sparse index type. #2251 - data = {"A": [0, 1]} - iframe = SparseDataFrame(data, default_kind="integer") - tm.assert_class_equal(iframe["A"].sp_index, iframe.iloc[:, 0].sp_index) - - def test_set_value(self, float_frame): - - # ok, as the index gets converted to object - frame = float_frame.copy() - res = frame._set_value("foobar", "B", 1.5) - assert res.index.dtype == "object" - - res = float_frame - res.index = res.index.astype(object) - - res = float_frame._set_value("foobar", "B", 1.5) - assert res is not float_frame - assert res.index[-1] == "foobar" - assert res._get_value("foobar", "B") == 1.5 - - res2 = res._set_value("foobar", "qux", 1.5) - assert res2 is not res - tm.assert_index_equal( - res2.columns, pd.Index(list(float_frame.columns) + ["qux"]) - ) - assert res2._get_value("foobar", "qux") == 1.5 - - def test_fancy_index_misc(self, float_frame): - # axis = 0 - sliced = float_frame.iloc[-2:, :] - expected = float_frame.reindex(index=float_frame.index[-2:]) - tm.assert_sp_frame_equal(sliced, expected) - - # axis = 1 - sliced = float_frame.iloc[:, -2:] - expected = float_frame.reindex(columns=float_frame.columns[-2:]) - tm.assert_sp_frame_equal(sliced, expected) - - def test_getitem_overload(self, float_frame): - # slicing - sl = float_frame[:20] - tm.assert_sp_frame_equal(sl, float_frame.reindex(float_frame.index[:20])) - - # boolean indexing - d = float_frame.index[5] - indexer = float_frame.index > d - - subindex = float_frame.index[indexer] - subframe = float_frame[indexer] - - tm.assert_index_equal(subindex, subframe.index) - msg = "Item wrong length 9 instead of 10" - with pytest.raises(ValueError, match=msg): - float_frame[indexer[:-1]] - - def test_setitem( - self, - float_frame, - float_frame_int_kind, - float_frame_dense, - float_frame_fill0, - float_frame_fill0_dense, - float_frame_fill2, - float_frame_fill2_dense, - ): - def _check_frame(frame, orig): - N = len(frame) - - # insert SparseSeries - frame["E"] = frame["A"] - assert isinstance(frame["E"], SparseSeries) - tm.assert_sp_series_equal(frame["E"], frame["A"], check_names=False) - - # insert SparseSeries differently-indexed - to_insert = frame["A"][::2] - frame["E"] = to_insert - expected = to_insert.to_dense().reindex(frame.index) - result = frame["E"].to_dense() - tm.assert_series_equal(result, expected, check_names=False) - assert result.name == "E" - - # insert Series - frame["F"] = frame["A"].to_dense() - assert isinstance(frame["F"], SparseSeries) - tm.assert_sp_series_equal(frame["F"], frame["A"], check_names=False) - - # insert Series differently-indexed - to_insert = frame["A"].to_dense()[::2] - frame["G"] = to_insert - expected = to_insert.reindex(frame.index) - expected.name = "G" - tm.assert_series_equal(frame["G"].to_dense(), expected) - - # insert ndarray - frame["H"] = np.random.randn(N) - assert isinstance(frame["H"], SparseSeries) - - to_sparsify = np.random.randn(N) - to_sparsify[N // 2 :] = frame.default_fill_value - frame["I"] = to_sparsify - assert len(frame["I"].sp_values) == N // 2 - - # insert ndarray wrong size - # GH 25484 - msg = "Length of values does not match length of index" - with pytest.raises(ValueError, match=msg): - frame["foo"] = np.random.randn(N - 1) - - # scalar value - frame["J"] = 5 - assert len(frame["J"].sp_values) == N - assert (frame["J"].sp_values == 5).all() - - frame["K"] = frame.default_fill_value - assert len(frame["K"].sp_values) == 0 - - _check_frame(float_frame, float_frame_dense) - _check_frame(float_frame_int_kind, float_frame_dense) - _check_frame(float_frame_fill0, float_frame_fill0_dense) - _check_frame(float_frame_fill2, float_frame_fill2_dense) - - @pytest.mark.parametrize( - "values", - [ - [True, False], - [0, 1], - [1, None], - ["a", "b"], - [pd.Timestamp("2017"), pd.NaT], - [pd.Timedelta("10s"), pd.NaT], - ], - ) - def test_setitem_more(self, values): - df = pd.DataFrame({"A": values}) - df["A"] = pd.SparseArray(values) - expected = pd.DataFrame({"A": pd.SparseArray(values)}) - tm.assert_frame_equal(df, expected) - - def test_setitem_corner(self, float_frame): - float_frame["a"] = float_frame["B"] - tm.assert_sp_series_equal(float_frame["a"], float_frame["B"], check_names=False) - - def test_setitem_array(self, float_frame): - arr = float_frame["B"] - - float_frame["E"] = arr - tm.assert_sp_series_equal(float_frame["E"], float_frame["B"], check_names=False) - - float_frame["F"] = arr[:-1] - index = float_frame.index[:-1] - tm.assert_sp_series_equal( - float_frame["E"].reindex(index), - float_frame["F"].reindex(index), - check_names=False, - ) - - def test_setitem_chained_no_consolidate(self): - # https://github.com/pandas-dev/pandas/pull/19268 - # issuecomment-361696418 - # chained setitem used to cause consolidation - sdf = pd.SparseDataFrame([[np.nan, 1], [2, np.nan]]) - with pd.option_context("mode.chained_assignment", None): - sdf[0][1] = 2 - assert len(sdf._data.blocks) == 2 - - def test_delitem(self, float_frame): - A = float_frame["A"] - C = float_frame["C"] - - del float_frame["B"] - assert "B" not in float_frame - tm.assert_sp_series_equal(float_frame["A"], A) - tm.assert_sp_series_equal(float_frame["C"], C) - - del float_frame["D"] - assert "D" not in float_frame - - del float_frame["A"] - assert "A" not in float_frame - - def test_set_columns(self, float_frame): - float_frame.columns = float_frame.columns - msg = ( - "Length mismatch: Expected axis has 4 elements, new values have" - " 3 elements" - ) - with pytest.raises(ValueError, match=msg): - float_frame.columns = float_frame.columns[:-1] - - def test_set_index(self, float_frame): - float_frame.index = float_frame.index - msg = ( - "Length mismatch: Expected axis has 10 elements, new values" - " have 9 elements" - ) - with pytest.raises(ValueError, match=msg): - float_frame.index = float_frame.index[:-1] - - def test_ctor_reindex(self): - idx = pd.Index([0, 1, 2, 3]) - msg = "Length of passed values is 2, index implies 4" - with pytest.raises(ValueError, match=msg): - pd.SparseDataFrame({"A": [1, 2]}, index=idx) - - def test_append(self, float_frame): - a = float_frame[:5] - b = float_frame[5:] - - appended = a.append(b) - tm.assert_sp_frame_equal(appended, float_frame, exact_indices=False) - - a = float_frame.iloc[:5, :3] - b = float_frame.iloc[5:] - with tm.assert_produces_warning( - FutureWarning, check_stacklevel=False, raise_on_extra_warnings=False - ): - # Stacklevel is set for pd.concat, not append - appended = a.append(b) - tm.assert_sp_frame_equal( - appended.iloc[:, :3], float_frame.iloc[:, :3], exact_indices=False - ) - - a = a[["B", "C", "A"]].head(2) - b = b.head(2) - - expected = pd.SparseDataFrame( - { - "B": [0.0, 1, None, 3], - "C": [0.0, 1, 5, 6], - "A": [None, None, 2, 3], - "D": [None, None, 5, None], - }, - index=a.index | b.index, - columns=["B", "C", "A", "D"], - ) - with tm.assert_produces_warning(None, raise_on_extra_warnings=False): - appended = a.append(b, sort=False) - - tm.assert_frame_equal(appended, expected) - - with tm.assert_produces_warning(None, raise_on_extra_warnings=False): - appended = a.append(b, sort=True) - - tm.assert_sp_frame_equal( - appended, - expected[["A", "B", "C", "D"]], - consolidate_block_indices=True, - check_kind=False, - ) - - def test_astype(self): - sparse = pd.SparseDataFrame( - { - "A": SparseArray([1, 2, 3, 4], dtype=np.int64), - "B": SparseArray([4, 5, 6, 7], dtype=np.int64), - } - ) - assert sparse["A"].dtype == SparseDtype(np.int64) - assert sparse["B"].dtype == SparseDtype(np.int64) - - # retain fill_value - res = sparse.astype(np.float64) - exp = pd.SparseDataFrame( - { - "A": SparseArray([1.0, 2.0, 3.0, 4.0], fill_value=0, kind="integer"), - "B": SparseArray([4.0, 5.0, 6.0, 7.0], fill_value=0, kind="integer"), - }, - default_fill_value=np.nan, - ) - tm.assert_sp_frame_equal(res, exp) - assert res["A"].dtype == SparseDtype(np.float64, 0) - assert res["B"].dtype == SparseDtype(np.float64, 0) - - # update fill_value - res = sparse.astype(SparseDtype(np.float64, np.nan)) - exp = pd.SparseDataFrame( - { - "A": SparseArray( - [1.0, 2.0, 3.0, 4.0], fill_value=np.nan, kind="integer" - ), - "B": SparseArray( - [4.0, 5.0, 6.0, 7.0], fill_value=np.nan, kind="integer" - ), - }, - default_fill_value=np.nan, - ) - tm.assert_sp_frame_equal(res, exp) - assert res["A"].dtype == SparseDtype(np.float64, np.nan) - assert res["B"].dtype == SparseDtype(np.float64, np.nan) - - def test_astype_bool(self): - sparse = pd.SparseDataFrame( - { - "A": SparseArray([0, 2, 0, 4], fill_value=0, dtype=np.int64), - "B": SparseArray([0, 5, 0, 7], fill_value=0, dtype=np.int64), - }, - default_fill_value=0, - ) - assert sparse["A"].dtype == SparseDtype(np.int64) - assert sparse["B"].dtype == SparseDtype(np.int64) - - res = sparse.astype(SparseDtype(bool, False)) - exp = pd.SparseDataFrame( - { - "A": SparseArray( - [False, True, False, True], - dtype=np.bool, - fill_value=False, - kind="integer", - ), - "B": SparseArray( - [False, True, False, True], - dtype=np.bool, - fill_value=False, - kind="integer", - ), - }, - default_fill_value=False, - ) - tm.assert_sp_frame_equal(res, exp) - assert res["A"].dtype == SparseDtype(np.bool) - assert res["B"].dtype == SparseDtype(np.bool) - - def test_astype_object(self): - # This may change in GH-23125 - df = pd.DataFrame({"A": SparseArray([0, 1]), "B": SparseArray([0, 1])}) - result = df.astype(object) - dtype = SparseDtype(object, 0) - expected = pd.DataFrame( - { - "A": SparseArray([0, 1], dtype=dtype), - "B": SparseArray([0, 1], dtype=dtype), - } - ) - tm.assert_frame_equal(result, expected) - - def test_fillna(self, float_frame_fill0, float_frame_fill0_dense): - df = float_frame_fill0.reindex(list(range(5))) - dense = float_frame_fill0_dense.reindex(list(range(5))) - - result = df.fillna(0) - expected = dense.fillna(0) - tm.assert_sp_frame_equal( - result, expected.to_sparse(fill_value=0), exact_indices=False - ) - tm.assert_frame_equal(result.to_dense(), expected) - - result = df.copy() - result.fillna(0, inplace=True) - expected = dense.fillna(0) - - tm.assert_sp_frame_equal( - result, expected.to_sparse(fill_value=0), exact_indices=False - ) - tm.assert_frame_equal(result.to_dense(), expected) - - result = df.copy() - result = df["A"] - result.fillna(0, inplace=True) - - expected = dense["A"].fillna(0) - # this changes internal SparseArray repr - # tm.assert_sp_series_equal(result, expected.to_sparse(fill_value=0)) - tm.assert_series_equal(result.to_dense(), expected) - - def test_fillna_fill_value(self): - df = pd.DataFrame({"A": [1, 0, 0], "B": [np.nan, np.nan, 4]}) - - sparse = pd.SparseDataFrame(df) - tm.assert_frame_equal( - sparse.fillna(-1).to_dense(), df.fillna(-1), check_dtype=False - ) - - sparse = pd.SparseDataFrame(df, default_fill_value=0) - tm.assert_frame_equal( - sparse.fillna(-1).to_dense(), df.fillna(-1), check_dtype=False - ) - - def test_sparse_frame_pad_backfill_limit(self): - index = np.arange(10) - df = DataFrame(np.random.randn(10, 4), index=index) - sdf = df.to_sparse() - - result = sdf[:2].reindex(index, method="pad", limit=5) - - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - expected = sdf[:2].reindex(index).fillna(method="pad") - expected = expected.to_dense() - expected.values[-3:] = np.nan - expected = expected.to_sparse() - tm.assert_frame_equal(result, expected) - - result = sdf[-2:].reindex(index, method="backfill", limit=5) - - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - expected = sdf[-2:].reindex(index).fillna(method="backfill") - expected = expected.to_dense() - expected.values[:3] = np.nan - expected = expected.to_sparse() - tm.assert_frame_equal(result, expected) - - def test_sparse_frame_fillna_limit(self): - index = np.arange(10) - df = DataFrame(np.random.randn(10, 4), index=index) - sdf = df.to_sparse() - - result = sdf[:2].reindex(index) - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - result = result.fillna(method="pad", limit=5) - - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - expected = sdf[:2].reindex(index).fillna(method="pad") - expected = expected.to_dense() - expected.values[-3:] = np.nan - expected = expected.to_sparse() - tm.assert_frame_equal(result, expected) - - result = sdf[-2:].reindex(index) - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - result = result.fillna(method="backfill", limit=5) - - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - expected = sdf[-2:].reindex(index).fillna(method="backfill") - expected = expected.to_dense() - expected.values[:3] = np.nan - expected = expected.to_sparse() - tm.assert_frame_equal(result, expected) - - def test_rename(self, float_frame): - result = float_frame.rename(index=str) - expected = SparseDataFrame( - float_frame.values, - index=float_frame.index.strftime("%Y-%m-%d %H:%M:%S"), - columns=list("ABCD"), - ) - tm.assert_sp_frame_equal(result, expected) - - result = float_frame.rename(columns="{}1".format) - data = { - "A1": [nan, nan, nan, 0, 1, 2, 3, 4, 5, 6], - "B1": [0, 1, 2, nan, nan, nan, 3, 4, 5, 6], - "C1": np.arange(10, dtype=np.float64), - "D1": [0, 1, 2, 3, 4, 5, nan, nan, nan, nan], - } - expected = SparseDataFrame(data, index=float_frame.index) - tm.assert_sp_frame_equal(result, expected) - - def test_corr(self, float_frame): - res = float_frame.corr() - # XXX: this stays sparse - tm.assert_frame_equal(res, float_frame.to_dense().corr().to_sparse()) - - def test_describe(self, float_frame): - float_frame["foo"] = np.nan - float_frame.dtypes.value_counts() - str(float_frame) - desc = float_frame.describe() # noqa - - def test_join(self, float_frame): - left = float_frame.loc[:, ["A", "B"]] - right = float_frame.loc[:, ["C", "D"]] - joined = left.join(right) - tm.assert_sp_frame_equal(joined, float_frame, exact_indices=False) - - right = float_frame.loc[:, ["B", "D"]] - msg = ( - r"columns overlap but no suffix specified: Index\(\['B'\]," - r" dtype='object'\)" - ) - with pytest.raises(ValueError, match=msg): - left.join(right) - - with pytest.raises(ValueError, match="Other Series must have a name"): - float_frame.join( - Series(np.random.randn(len(float_frame)), index=float_frame.index) - ) - - def test_reindex( - self, float_frame, float_frame_int_kind, float_frame_fill0, float_frame_fill2 - ): - def _check_frame(frame): - index = frame.index - sidx = index[::2] - sidx2 = index[:5] # noqa - - sparse_result = frame.reindex(sidx) - dense_result = frame.to_dense().reindex(sidx) - tm.assert_frame_equal(sparse_result.to_dense(), dense_result) - - tm.assert_frame_equal(frame.reindex(list(sidx)).to_dense(), dense_result) - - sparse_result2 = sparse_result.reindex(index) - dense_result2 = dense_result.reindex(index) - tm.assert_frame_equal(sparse_result2.to_dense(), dense_result2) - - # propagate CORRECT fill value - tm.assert_almost_equal( - sparse_result.default_fill_value, frame.default_fill_value - ) - tm.assert_almost_equal(sparse_result["A"].fill_value, frame["A"].fill_value) - - # length zero - length_zero = frame.reindex([]) - assert len(length_zero) == 0 - assert len(length_zero.columns) == len(frame.columns) - assert len(length_zero["A"]) == 0 - - # frame being reindexed has length zero - length_n = length_zero.reindex(index) - assert len(length_n) == len(frame) - assert len(length_n.columns) == len(frame.columns) - assert len(length_n["A"]) == len(frame) - - # reindex columns - reindexed = frame.reindex(columns=["A", "B", "Z"]) - assert len(reindexed.columns) == 3 - tm.assert_almost_equal(reindexed["Z"].fill_value, frame.default_fill_value) - assert np.isnan(reindexed["Z"].sp_values).all() - - _check_frame(float_frame) - _check_frame(float_frame_int_kind) - _check_frame(float_frame_fill0) - _check_frame(float_frame_fill2) - - # with copy=False - reindexed = float_frame.reindex(float_frame.index, copy=False) - reindexed["F"] = reindexed["A"] - assert "F" in float_frame - - reindexed = float_frame.reindex(float_frame.index) - reindexed["G"] = reindexed["A"] - assert "G" not in float_frame - - def test_reindex_fill_value(self, float_frame_fill0, float_frame_fill0_dense): - rng = bdate_range("20110110", periods=20) - - result = float_frame_fill0.reindex(rng, fill_value=0) - exp = float_frame_fill0_dense.reindex(rng, fill_value=0) - exp = exp.to_sparse(float_frame_fill0.default_fill_value) - tm.assert_sp_frame_equal(result, exp) - - def test_reindex_method(self): - - sparse = SparseDataFrame( - data=[[11.0, 12.0, 14.0], [21.0, 22.0, 24.0], [41.0, 42.0, 44.0]], - index=[1, 2, 4], - columns=[1, 2, 4], - dtype=float, - ) - - # Over indices - - # default method - result = sparse.reindex(index=range(6)) - expected = SparseDataFrame( - data=[ - [nan, nan, nan], - [11.0, 12.0, 14.0], - [21.0, 22.0, 24.0], - [nan, nan, nan], - [41.0, 42.0, 44.0], - [nan, nan, nan], - ], - index=range(6), - columns=[1, 2, 4], - dtype=float, - ) - tm.assert_sp_frame_equal(result, expected) - - # method='bfill' - result = sparse.reindex(index=range(6), method="bfill") - expected = SparseDataFrame( - data=[ - [11.0, 12.0, 14.0], - [11.0, 12.0, 14.0], - [21.0, 22.0, 24.0], - [41.0, 42.0, 44.0], - [41.0, 42.0, 44.0], - [nan, nan, nan], - ], - index=range(6), - columns=[1, 2, 4], - dtype=float, - ) - tm.assert_sp_frame_equal(result, expected) - - # method='ffill' - result = sparse.reindex(index=range(6), method="ffill") - expected = SparseDataFrame( - data=[ - [nan, nan, nan], - [11.0, 12.0, 14.0], - [21.0, 22.0, 24.0], - [21.0, 22.0, 24.0], - [41.0, 42.0, 44.0], - [41.0, 42.0, 44.0], - ], - index=range(6), - columns=[1, 2, 4], - dtype=float, - ) - tm.assert_sp_frame_equal(result, expected) - - # Over columns - - # default method - result = sparse.reindex(columns=range(6)) - expected = SparseDataFrame( - data=[ - [nan, 11.0, 12.0, nan, 14.0, nan], - [nan, 21.0, 22.0, nan, 24.0, nan], - [nan, 41.0, 42.0, nan, 44.0, nan], - ], - index=[1, 2, 4], - columns=range(6), - dtype=float, - ) - tm.assert_sp_frame_equal(result, expected) - - # method='bfill' - with pytest.raises(NotImplementedError): - sparse.reindex(columns=range(6), method="bfill") - - # method='ffill' - with pytest.raises(NotImplementedError): - sparse.reindex(columns=range(6), method="ffill") - - def test_take(self, float_frame): - result = float_frame.take([1, 0, 2], axis=1) - expected = float_frame.reindex(columns=["B", "A", "C"]) - tm.assert_sp_frame_equal(result, expected) - - def test_to_dense( - self, - float_frame, - float_frame_int_kind, - float_frame_dense, - float_frame_fill0, - float_frame_fill0_dense, - float_frame_fill2, - float_frame_fill2_dense, - ): - def _check(frame, orig): - dense_dm = frame.to_dense() - # Sparse[float] != float - tm.assert_frame_equal(frame, dense_dm, check_dtype=False) - tm.assert_frame_equal(dense_dm, orig, check_dtype=False) - - _check(float_frame, float_frame_dense) - _check(float_frame_int_kind, float_frame_dense) - _check(float_frame_fill0, float_frame_fill0_dense) - _check(float_frame_fill2, float_frame_fill2_dense) - - def test_stack_sparse_frame( - self, float_frame, float_frame_int_kind, float_frame_fill0, float_frame_fill2 - ): - def _check(frame): - dense_frame = frame.to_dense() # noqa - - from_dense_lp = frame.stack().to_frame() - - from_sparse_lp = spf.stack_sparse_frame(frame) - - tm.assert_numpy_array_equal(from_dense_lp.values, from_sparse_lp.values) - - _check(float_frame) - _check(float_frame_int_kind) - - # for now - msg = "This routine assumes NaN fill value" - with pytest.raises(TypeError, match=msg): - _check(float_frame_fill0) - with pytest.raises(TypeError, match=msg): - _check(float_frame_fill2) - - def test_transpose( - self, - float_frame, - float_frame_int_kind, - float_frame_dense, - float_frame_fill0, - float_frame_fill0_dense, - float_frame_fill2, - float_frame_fill2_dense, - ): - def _check(frame, orig): - transposed = frame.T - untransposed = transposed.T - tm.assert_sp_frame_equal(frame, untransposed) - - tm.assert_frame_equal(frame.T.to_dense(), orig.T) - tm.assert_frame_equal(frame.T.T.to_dense(), orig.T.T) - tm.assert_sp_frame_equal(frame, frame.T.T, exact_indices=False) - - _check(float_frame, float_frame_dense) - _check(float_frame_int_kind, float_frame_dense) - _check(float_frame_fill0, float_frame_fill0_dense) - _check(float_frame_fill2, float_frame_fill2_dense) - - def test_shift( - self, - float_frame, - float_frame_int_kind, - float_frame_dense, - float_frame_fill0, - float_frame_fill0_dense, - float_frame_fill2, - float_frame_fill2_dense, - ): - def _check(frame, orig): - shifted = frame.shift(0) - exp = orig.shift(0) - tm.assert_frame_equal(shifted.to_dense(), exp) - - shifted = frame.shift(1) - exp = orig.shift(1) - tm.assert_frame_equal(shifted.to_dense(), exp) - - shifted = frame.shift(-2) - exp = orig.shift(-2) - tm.assert_frame_equal(shifted.to_dense(), exp) - - shifted = frame.shift(2, freq="B") - exp = orig.shift(2, freq="B") - exp = exp.to_sparse(frame.default_fill_value, kind=frame.default_kind) - tm.assert_frame_equal(shifted, exp) - - shifted = frame.shift(2, freq=BDay()) - exp = orig.shift(2, freq=BDay()) - exp = exp.to_sparse(frame.default_fill_value, kind=frame.default_kind) - tm.assert_frame_equal(shifted, exp) - - _check(float_frame, float_frame_dense) - _check(float_frame_int_kind, float_frame_dense) - _check(float_frame_fill0, float_frame_fill0_dense) - _check(float_frame_fill2, float_frame_fill2_dense) - - def test_count(self, float_frame): - dense_result = float_frame.to_dense().count() - - result = float_frame.count() - tm.assert_series_equal(result.to_dense(), dense_result) - - result = float_frame.count(axis=None) - tm.assert_series_equal(result.to_dense(), dense_result) - - result = float_frame.count(axis=0) - tm.assert_series_equal(result.to_dense(), dense_result) - - result = float_frame.count(axis=1) - dense_result = float_frame.to_dense().count(axis=1) - - # win32 don't check dtype - tm.assert_series_equal(result, dense_result, check_dtype=False) - - def test_numpy_transpose(self): - sdf = SparseDataFrame([1, 2, 3], index=[1, 2, 3], columns=["a"]) - result = np.transpose(np.transpose(sdf)) - tm.assert_sp_frame_equal(result, sdf) - - msg = "the 'axes' parameter is not supported" - with pytest.raises(ValueError, match=msg): - np.transpose(sdf, axes=1) - - def test_combine_first(self, float_frame): - df = float_frame - - result = df[::2].combine_first(df) - - expected = df[::2].to_dense().combine_first(df.to_dense()) - expected = expected.to_sparse(fill_value=df.default_fill_value) - - tm.assert_sp_frame_equal(result, expected) - - @pytest.mark.xfail(reason="No longer supported.") - def test_combine_first_with_dense(self): - # We could support this if we allow - # pd.core.dtypes.cast.find_common_type to special case SparseDtype - # but I don't think that's worth it. - df = self.frame - - result = df[::2].combine_first(df.to_dense()) - expected = df[::2].to_dense().combine_first(df.to_dense()) - expected = expected.to_sparse(fill_value=df.default_fill_value) - - tm.assert_sp_frame_equal(result, expected) - - def test_combine_add(self, float_frame): - df = float_frame.to_dense() - df2 = df.copy() - df2["C"][:3] = np.nan - df["A"][:3] = 5.7 - - result = df.to_sparse().add(df2.to_sparse(), fill_value=0) - expected = df.add(df2, fill_value=0).to_sparse() - tm.assert_sp_frame_equal(result, expected) - - def test_isin(self): - sparse_df = DataFrame({"flag": [1.0, 0.0, 1.0]}).to_sparse(fill_value=0.0) - xp = sparse_df[sparse_df.flag == 1.0] - rs = sparse_df[sparse_df.flag.isin([1.0])] - tm.assert_frame_equal(xp, rs) - - def test_sparse_pow_issue(self): - # 2220 - df = SparseDataFrame({"A": [1.1, 3.3], "B": [2.5, -3.9]}) - - # note : no error without nan - df = SparseDataFrame({"A": [nan, 0, 1]}) - - # note that 2 ** df works fine, also df ** 1 - result = 1 ** df - - r1 = result.take([0], 1)["A"] - r2 = result["A"] - - assert len(r2.sp_values) == len(r1.sp_values) - - def test_as_blocks(self): - df = SparseDataFrame({"A": [1.1, 3.3], "B": [nan, -3.9]}, dtype="float64") - - # deprecated 0.21.0 - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): - df_blocks = df.blocks - assert list(df_blocks.keys()) == ["Sparse[float64, nan]"] - tm.assert_frame_equal(df_blocks["Sparse[float64, nan]"], df) - - @pytest.mark.xfail(reason="nan column names in _init_dict problematic (GH#16894)") - def test_nan_columnname(self): - # GH 8822 - nan_colname = DataFrame(Series(1.0, index=[0]), columns=[nan]) - nan_colname_sparse = nan_colname.to_sparse() - assert np.isnan(nan_colname_sparse.columns[0]) - - def test_isna(self): - # GH 8276 - df = pd.SparseDataFrame( - {"A": [np.nan, np.nan, 1, 2, np.nan], "B": [0, np.nan, np.nan, 2, np.nan]} - ) - - res = df.isna() - exp = pd.SparseDataFrame( - { - "A": [True, True, False, False, True], - "B": [False, True, True, False, True], - }, - default_fill_value=True, - ) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(res, exp) - - # if fill_value is not nan, True can be included in sp_values - df = pd.SparseDataFrame( - {"A": [0, 0, 1, 2, np.nan], "B": [0, np.nan, 0, 2, np.nan]}, - default_fill_value=0.0, - ) - res = df.isna() - assert isinstance(res, pd.SparseDataFrame) - exp = pd.DataFrame( - { - "A": [False, False, False, False, True], - "B": [False, True, False, False, True], - } - ) - tm.assert_frame_equal(res.to_dense(), exp) - - def test_notna(self): - # GH 8276 - df = pd.SparseDataFrame( - {"A": [np.nan, np.nan, 1, 2, np.nan], "B": [0, np.nan, np.nan, 2, np.nan]} - ) - - res = df.notna() - exp = pd.SparseDataFrame( - { - "A": [False, False, True, True, False], - "B": [True, False, False, True, False], - }, - default_fill_value=False, - ) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(res, exp) - - # if fill_value is not nan, True can be included in sp_values - df = pd.SparseDataFrame( - {"A": [0, 0, 1, 2, np.nan], "B": [0, np.nan, 0, 2, np.nan]}, - default_fill_value=0.0, - ) - res = df.notna() - assert isinstance(res, pd.SparseDataFrame) - exp = pd.DataFrame( - { - "A": [True, True, True, True, False], - "B": [True, False, True, True, False], - } - ) - tm.assert_frame_equal(res.to_dense(), exp) - - def test_default_fill_value_with_no_data(self): - # GH 16807 - expected = pd.SparseDataFrame( - [[1.0, 1.0], [1.0, 1.0]], columns=list("ab"), index=range(2) - ) - result = pd.SparseDataFrame( - columns=list("ab"), index=range(2), default_fill_value=1.0 - ) - tm.assert_frame_equal(expected, result) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:DataFrame.to_sparse:FutureWarning") -class TestSparseDataFrameArithmetic: - def test_numeric_op_scalar(self): - df = pd.DataFrame( - { - "A": [nan, nan, 0, 1], - "B": [0, 1, 2, nan], - "C": [1.0, 2.0, 3.0, 4.0], - "D": [nan, nan, nan, nan], - } - ) - sparse = df.to_sparse() - - tm.assert_sp_frame_equal(sparse + 1, (df + 1).to_sparse()) - - def test_comparison_op_scalar(self): - # GH 13001 - df = pd.DataFrame( - { - "A": [nan, nan, 0, 1], - "B": [0, 1, 2, nan], - "C": [1.0, 2.0, 3.0, 4.0], - "D": [nan, nan, nan, nan], - } - ) - sparse = df.to_sparse() - - # comparison changes internal repr, compare with dense - res = sparse > 1 - assert isinstance(res, pd.SparseDataFrame) - tm.assert_frame_equal(res.to_dense(), df > 1) - - res = sparse != 0 - assert isinstance(res, pd.SparseDataFrame) - tm.assert_frame_equal(res.to_dense(), df != 0) - - def test_add_series_retains_dtype(self): - # SparseDataFrame._combine_match_columns used to incorrectly cast - # to float - d = {0: [2j, 3j], 1: [0, 1]} - sdf = SparseDataFrame(data=d, default_fill_value=1) - result = sdf + sdf[0] - - df = sdf.to_dense() - expected = df + df[0] - tm.assert_frame_equal(result.to_dense(), expected) - - # Make it explicit to be on the safe side - edata = {0: [4j, 5j], 1: [3j, 1 + 3j]} - expected = DataFrame(edata) - tm.assert_frame_equal(result.to_dense(), expected) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:DataFrame.to_sparse:FutureWarning") -class TestSparseDataFrameAnalytics: - def test_cumsum(self, float_frame): - expected = SparseDataFrame(float_frame.to_dense().cumsum()) - - result = float_frame.cumsum() - tm.assert_sp_frame_equal(result, expected) - - result = float_frame.cumsum(axis=None) - tm.assert_sp_frame_equal(result, expected) - - result = float_frame.cumsum(axis=0) - tm.assert_sp_frame_equal(result, expected) - - def test_numpy_cumsum(self, float_frame): - result = np.cumsum(float_frame) - expected = SparseDataFrame(float_frame.to_dense().cumsum()) - tm.assert_sp_frame_equal(result, expected) - - msg = "the 'dtype' parameter is not supported" - with pytest.raises(ValueError, match=msg): - np.cumsum(float_frame, dtype=np.int64) - - msg = "the 'out' parameter is not supported" - with pytest.raises(ValueError, match=msg): - np.cumsum(float_frame, out=result) - - def test_numpy_func_call(self, float_frame): - # no exception should be raised even though - # numpy passes in 'axis=None' or `axis=-1' - funcs = ["sum", "cumsum", "var", "mean", "prod", "cumprod", "std", "min", "max"] - for func in funcs: - getattr(np, func)(float_frame) - - @pytest.mark.xfail(reason="Wrong SparseBlock initialization (GH 17386)") - def test_quantile(self): - # GH 17386 - data = [[1, 1], [2, 10], [3, 100], [nan, nan]] - q = 0.1 - - sparse_df = SparseDataFrame(data) - result = sparse_df.quantile(q) - - dense_df = DataFrame(data) - dense_expected = dense_df.quantile(q) - sparse_expected = SparseSeries(dense_expected) - - tm.assert_series_equal(result, dense_expected) - tm.assert_sp_series_equal(result, sparse_expected) - - @pytest.mark.xfail(reason="Wrong SparseBlock initialization (GH 17386)") - def test_quantile_multi(self): - # GH 17386 - data = [[1, 1], [2, 10], [3, 100], [nan, nan]] - q = [0.1, 0.5] - - sparse_df = SparseDataFrame(data) - result = sparse_df.quantile(q) - - dense_df = DataFrame(data) - dense_expected = dense_df.quantile(q) - sparse_expected = SparseDataFrame(dense_expected) - - tm.assert_frame_equal(result, dense_expected) - tm.assert_sp_frame_equal(result, sparse_expected) - - def test_assign_with_sparse_frame(self): - # GH 19163 - df = pd.DataFrame({"a": [1, 2, 3]}) - res = df.to_sparse(fill_value=False).assign(newcol=False) - exp = df.assign(newcol=False).to_sparse(fill_value=False) - - tm.assert_sp_frame_equal(res, exp) - - for column in res.columns: - assert type(res[column]) is SparseSeries - - @pytest.mark.parametrize("inplace", [True, False]) - @pytest.mark.parametrize("how", ["all", "any"]) - def test_dropna(self, inplace, how): - # Tests regression #21172. - expected = pd.SparseDataFrame({"F2": [0, 1]}) - input_df = pd.SparseDataFrame( - {"F1": [float("nan"), float("nan")], "F2": [0, 1]} - ) - result_df = input_df.dropna(axis=1, inplace=inplace, how=how) - if inplace: - result_df = input_df - tm.assert_sp_frame_equal(expected, result_df) diff --git a/pandas/tests/sparse/frame/test_indexing.py b/pandas/tests/sparse/frame/test_indexing.py deleted file mode 100644 index c93e9d1e0e8d1..0000000000000 --- a/pandas/tests/sparse/frame/test_indexing.py +++ /dev/null @@ -1,103 +0,0 @@ -import numpy as np -import pytest - -from pandas import DataFrame, SparseDataFrame -from pandas.util import testing as tm - -pytestmark = pytest.mark.skip("Wrong SparseBlock initialization (GH 17386)") - - -@pytest.mark.parametrize( - "data", - [ - [[1, 1], [2, 2], [3, 3], [4, 4], [0, 0]], - [[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [4.0, 4.0], [np.nan, np.nan]], - [ - [1.0, 1.0 + 1.0j], - [2.0 + 2.0j, 2.0], - [3.0, 3.0 + 3.0j], - [4.0 + 4.0j, 4.0], - [np.nan, np.nan], - ], - ], -) -@pytest.mark.xfail(reason="Wrong SparseBlock initialization (GH#17386)") -def test_where_with_numeric_data(data): - # GH 17386 - lower_bound = 1.5 - - sparse = SparseDataFrame(data) - result = sparse.where(sparse > lower_bound) - - dense = DataFrame(data) - dense_expected = dense.where(dense > lower_bound) - sparse_expected = SparseDataFrame(dense_expected) - - tm.assert_frame_equal(result, dense_expected) - tm.assert_sp_frame_equal(result, sparse_expected) - - -@pytest.mark.parametrize( - "data", - [ - [[1, 1], [2, 2], [3, 3], [4, 4], [0, 0]], - [[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [4.0, 4.0], [np.nan, np.nan]], - [ - [1.0, 1.0 + 1.0j], - [2.0 + 2.0j, 2.0], - [3.0, 3.0 + 3.0j], - [4.0 + 4.0j, 4.0], - [np.nan, np.nan], - ], - ], -) -@pytest.mark.parametrize("other", [True, -100, 0.1, 100.0 + 100.0j]) -@pytest.mark.xfail(reason="Wrong SparseBlock initialization (GH#17386)") -def test_where_with_numeric_data_and_other(data, other): - # GH 17386 - lower_bound = 1.5 - - sparse = SparseDataFrame(data) - result = sparse.where(sparse > lower_bound, other) - - dense = DataFrame(data) - dense_expected = dense.where(dense > lower_bound, other) - sparse_expected = SparseDataFrame(dense_expected, default_fill_value=other) - - tm.assert_frame_equal(result, dense_expected) - tm.assert_sp_frame_equal(result, sparse_expected) - - -@pytest.mark.xfail(reason="Wrong SparseBlock initialization (GH#17386)") -def test_where_with_bool_data(): - # GH 17386 - data = [[False, False], [True, True], [False, False]] - cond = True - - sparse = SparseDataFrame(data) - result = sparse.where(sparse == cond) - - dense = DataFrame(data) - dense_expected = dense.where(dense == cond) - sparse_expected = SparseDataFrame(dense_expected) - - tm.assert_frame_equal(result, dense_expected) - tm.assert_sp_frame_equal(result, sparse_expected) - - -@pytest.mark.parametrize("other", [True, 0, 0.1, 100.0 + 100.0j]) -@pytest.mark.xfail(reason="Wrong SparseBlock initialization (GH#17386)") -def test_where_with_bool_data_and_other(other): - # GH 17386 - data = [[False, False], [True, True], [False, False]] - cond = True - - sparse = SparseDataFrame(data) - result = sparse.where(sparse == cond, other) - - dense = DataFrame(data) - dense_expected = dense.where(dense == cond, other) - sparse_expected = SparseDataFrame(dense_expected, default_fill_value=other) - - tm.assert_frame_equal(result, dense_expected) - tm.assert_sp_frame_equal(result, sparse_expected) diff --git a/pandas/tests/sparse/frame/test_to_csv.py b/pandas/tests/sparse/frame/test_to_csv.py deleted file mode 100644 index 4ba4fba7391d4..0000000000000 --- a/pandas/tests/sparse/frame/test_to_csv.py +++ /dev/null @@ -1,24 +0,0 @@ -import numpy as np -import pytest - -from pandas import SparseDataFrame, read_csv -from pandas.util import testing as tm - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:DataFrame.to_sparse:FutureWarning") -class TestSparseDataFrameToCsv: - fill_values = [np.nan, 0, None, 1] - - @pytest.mark.parametrize("fill_value", fill_values) - def test_to_csv_sparse_dataframe(self, fill_value): - # GH19384 - sdf = SparseDataFrame( - {"a": type(self).fill_values}, default_fill_value=fill_value - ) - - with tm.ensure_clean("sparse_df.csv") as path: - sdf.to_csv(path, index=False) - df = read_csv(path, skip_blank_lines=False) - - tm.assert_sp_frame_equal(df.to_sparse(fill_value=fill_value), sdf) diff --git a/pandas/tests/sparse/frame/test_to_from_scipy.py b/pandas/tests/sparse/frame/test_to_from_scipy.py deleted file mode 100644 index 9d1ccc62146ab..0000000000000 --- a/pandas/tests/sparse/frame/test_to_from_scipy.py +++ /dev/null @@ -1,196 +0,0 @@ -import numpy as np -import pytest - -from pandas.core.dtypes.common import is_bool_dtype - -import pandas as pd -from pandas import SparseDataFrame, SparseSeries -from pandas.core.sparse.api import SparseDtype -from pandas.util import testing as tm - -scipy = pytest.importorskip("scipy") -ignore_matrix_warning = pytest.mark.filterwarnings( - "ignore:the matrix subclass:PendingDeprecationWarning" -) - - -@pytest.mark.parametrize("index", [None, list("abc")]) # noqa: F811 -@pytest.mark.parametrize("columns", [None, list("def")]) -@pytest.mark.parametrize("fill_value", [None, 0, np.nan]) -@pytest.mark.parametrize("dtype", [bool, int, float, np.uint16]) -@ignore_matrix_warning -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -def test_from_to_scipy(spmatrix, index, columns, fill_value, dtype): - # GH 4343 - # Make one ndarray and from it one sparse matrix, both to be used for - # constructing frames and comparing results - arr = np.eye(3, dtype=dtype) - # GH 16179 - arr[0, 1] = dtype(2) - try: - spm = spmatrix(arr) - assert spm.dtype == arr.dtype - except (TypeError, AssertionError): - # If conversion to sparse fails for this spmatrix type and arr.dtype, - # then the combination is not currently supported in NumPy, so we - # can just skip testing it thoroughly - return - - sdf = SparseDataFrame( - spm, index=index, columns=columns, default_fill_value=fill_value - ) - - # Expected result construction is kind of tricky for all - # dtype-fill_value combinations; easiest to cast to something generic - # and except later on - rarr = arr.astype(object) - rarr[arr == 0] = np.nan - expected = SparseDataFrame(rarr, index=index, columns=columns).fillna( - fill_value if fill_value is not None else np.nan - ) - - # Assert frame is as expected - sdf_obj = sdf.astype(object) - tm.assert_sp_frame_equal(sdf_obj, expected) - tm.assert_frame_equal(sdf_obj.to_dense(), expected.to_dense()) - - # Assert spmatrices equal - assert dict(sdf.to_coo().todok()) == dict(spm.todok()) - - # Ensure dtype is preserved if possible - # XXX: verify this - res_dtype = bool if is_bool_dtype(dtype) else dtype - tm.assert_contains_all( - sdf.dtypes.apply(lambda dtype: dtype.subtype), {np.dtype(res_dtype)} - ) - assert sdf.to_coo().dtype == res_dtype - - # However, adding a str column results in an upcast to object - sdf["strings"] = np.arange(len(sdf)).astype(str) - assert sdf.to_coo().dtype == np.object_ - - -@pytest.mark.parametrize("fill_value", [None, 0, np.nan]) # noqa: F811 -@ignore_matrix_warning -@pytest.mark.filterwarnings("ignore:object dtype is not supp:UserWarning") -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -def test_from_to_scipy_object(spmatrix, fill_value): - # GH 4343 - dtype = object - columns = list("cd") - index = list("ab") - - if spmatrix is scipy.sparse.dok_matrix: - pytest.skip("dok_matrix from object does not work in SciPy") - - # Make one ndarray and from it one sparse matrix, both to be used for - # constructing frames and comparing results - arr = np.eye(2, dtype=dtype) - try: - spm = spmatrix(arr) - assert spm.dtype == arr.dtype - except (TypeError, AssertionError): - # If conversion to sparse fails for this spmatrix type and arr.dtype, - # then the combination is not currently supported in NumPy, so we - # can just skip testing it thoroughly - return - - sdf = SparseDataFrame( - spm, index=index, columns=columns, default_fill_value=fill_value - ) - - # Expected result construction is kind of tricky for all - # dtype-fill_value combinations; easiest to cast to something generic - # and except later on - rarr = arr.astype(object) - rarr[arr == 0] = np.nan - expected = SparseDataFrame(rarr, index=index, columns=columns).fillna( - fill_value if fill_value is not None else np.nan - ) - - # Assert frame is as expected - sdf_obj = sdf.astype(SparseDtype(object, fill_value)) - tm.assert_sp_frame_equal(sdf_obj, expected) - tm.assert_frame_equal(sdf_obj.to_dense(), expected.to_dense()) - - # Assert spmatrices equal - assert dict(sdf.to_coo().todok()) == dict(spm.todok()) - - # Ensure dtype is preserved if possible - res_dtype = object - tm.assert_contains_all( - sdf.dtypes.apply(lambda dtype: dtype.subtype), {np.dtype(res_dtype)} - ) - assert sdf.to_coo().dtype == res_dtype - - -@ignore_matrix_warning -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -def test_from_scipy_correct_ordering(spmatrix): - # GH 16179 - arr = np.arange(1, 5).reshape(2, 2) - try: - spm = spmatrix(arr) - assert spm.dtype == arr.dtype - except (TypeError, AssertionError): - # If conversion to sparse fails for this spmatrix type and arr.dtype, - # then the combination is not currently supported in NumPy, so we - # can just skip testing it thoroughly - return - - sdf = SparseDataFrame(spm) - expected = SparseDataFrame(arr) - tm.assert_sp_frame_equal(sdf, expected) - tm.assert_frame_equal(sdf.to_dense(), expected.to_dense()) - - -@ignore_matrix_warning -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -def test_from_scipy_fillna(spmatrix): - # GH 16112 - arr = np.eye(3) - arr[1:, 0] = np.nan - - try: - spm = spmatrix(arr) - assert spm.dtype == arr.dtype - except (TypeError, AssertionError): - # If conversion to sparse fails for this spmatrix type and arr.dtype, - # then the combination is not currently supported in NumPy, so we - # can just skip testing it thoroughly - return - - sdf = SparseDataFrame(spm).fillna(-1.0) - - # Returning frame should fill all nan values with -1.0 - expected = SparseDataFrame( - { - 0: SparseSeries([1.0, -1, -1]), - 1: SparseSeries([np.nan, 1, np.nan]), - 2: SparseSeries([np.nan, np.nan, 1]), - }, - default_fill_value=-1, - ) - - # fill_value is expected to be what .fillna() above was called with - # We don't use -1 as initial fill_value in expected SparseSeries - # construction because this way we obtain "compressed" SparseArrays, - # avoiding having to construct them ourselves - for col in expected: - expected[col].fill_value = -1 - - tm.assert_sp_frame_equal(sdf, expected) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") -def test_index_names_multiple_nones(): - # https://github.com/pandas-dev/pandas/pull/24092 - sparse = pytest.importorskip("scipy.sparse") - - s = pd.Series(1, index=pd.MultiIndex.from_product([["A", "B"], [0, 1]])).to_sparse() - result, _, _ = s.to_coo() - assert isinstance(result, sparse.coo_matrix) - result = result.toarray() - expected = np.ones((2, 2), dtype="int64") - tm.assert_numpy_array_equal(result, expected) diff --git a/pandas/tests/sparse/series/__init__.py b/pandas/tests/sparse/series/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/pandas/tests/sparse/series/test_indexing.py b/pandas/tests/sparse/series/test_indexing.py deleted file mode 100644 index c75f3b2134f91..0000000000000 --- a/pandas/tests/sparse/series/test_indexing.py +++ /dev/null @@ -1,113 +0,0 @@ -import numpy as np -import pytest - -from pandas import Series, SparseSeries -from pandas.util import testing as tm - -pytestmark = pytest.mark.skip("Wrong SparseBlock initialization (GH 17386)") - - -@pytest.mark.parametrize( - "data", - [ - [1, 1, 2, 2, 3, 3, 4, 4, 0, 0], - [1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0, 4.0, np.nan, np.nan], - [ - 1.0, - 1.0 + 1.0j, - 2.0 + 2.0j, - 2.0, - 3.0, - 3.0 + 3.0j, - 4.0 + 4.0j, - 4.0, - np.nan, - np.nan, - ], - ], -) -@pytest.mark.xfail(reason="Wrong SparseBlock initialization (GH#17386)") -def test_where_with_numeric_data(data): - # GH 17386 - lower_bound = 1.5 - - sparse = SparseSeries(data) - result = sparse.where(sparse > lower_bound) - - dense = Series(data) - dense_expected = dense.where(dense > lower_bound) - sparse_expected = SparseSeries(dense_expected) - - tm.assert_series_equal(result, dense_expected) - tm.assert_sp_series_equal(result, sparse_expected) - - -@pytest.mark.parametrize( - "data", - [ - [1, 1, 2, 2, 3, 3, 4, 4, 0, 0], - [1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0, 4.0, np.nan, np.nan], - [ - 1.0, - 1.0 + 1.0j, - 2.0 + 2.0j, - 2.0, - 3.0, - 3.0 + 3.0j, - 4.0 + 4.0j, - 4.0, - np.nan, - np.nan, - ], - ], -) -@pytest.mark.parametrize("other", [True, -100, 0.1, 100.0 + 100.0j]) -@pytest.mark.skip(reason="Wrong SparseBlock initialization (Segfault) (GH 17386)") -def test_where_with_numeric_data_and_other(data, other): - # GH 17386 - lower_bound = 1.5 - - sparse = SparseSeries(data) - result = sparse.where(sparse > lower_bound, other) - - dense = Series(data) - dense_expected = dense.where(dense > lower_bound, other) - sparse_expected = SparseSeries(dense_expected, fill_value=other) - - tm.assert_series_equal(result, dense_expected) - tm.assert_sp_series_equal(result, sparse_expected) - - -@pytest.mark.xfail(reason="Wrong SparseBlock initialization (GH#17386)") -def test_where_with_bool_data(): - # GH 17386 - data = [False, False, True, True, False, False] - cond = True - - sparse = SparseSeries(data) - result = sparse.where(sparse == cond) - - dense = Series(data) - dense_expected = dense.where(dense == cond) - sparse_expected = SparseSeries(dense_expected) - - tm.assert_series_equal(result, dense_expected) - tm.assert_sp_series_equal(result, sparse_expected) - - -@pytest.mark.parametrize("other", [True, 0, 0.1, 100.0 + 100.0j]) -@pytest.mark.skip(reason="Wrong SparseBlock initialization (Segfault) (GH 17386)") -def test_where_with_bool_data_and_other(other): - # GH 17386 - data = [False, False, True, True, False, False] - cond = True - - sparse = SparseSeries(data) - result = sparse.where(sparse == cond, other) - - dense = Series(data) - dense_expected = dense.where(dense == cond, other) - sparse_expected = SparseSeries(dense_expected, fill_value=other) - - tm.assert_series_equal(result, dense_expected) - tm.assert_sp_series_equal(result, sparse_expected) diff --git a/pandas/tests/sparse/series/test_series.py b/pandas/tests/sparse/series/test_series.py deleted file mode 100644 index 046e7745fd4ec..0000000000000 --- a/pandas/tests/sparse/series/test_series.py +++ /dev/null @@ -1,1596 +0,0 @@ -from datetime import datetime -import operator - -import numpy as np -from numpy import nan -import pytest - -from pandas._libs.sparse import BlockIndex, IntIndex -from pandas.compat import PY36 -from pandas.errors import PerformanceWarning -import pandas.util._test_decorators as td - -import pandas as pd -from pandas import DataFrame, Series, SparseDtype, SparseSeries, bdate_range, isna -from pandas.core import ops -from pandas.core.reshape.util import cartesian_product -import pandas.core.sparse.frame as spf -from pandas.tests.series.test_api import SharedWithSparse -import pandas.util.testing as tm - -from pandas.tseries.offsets import BDay - - -def test_deprecated(): - with tm.assert_produces_warning(FutureWarning): - pd.SparseSeries([0, 1]) - - -def _test_data1(): - # nan-based - arr = np.arange(20, dtype=float) - index = np.arange(20) - arr[:2] = nan - arr[5:10] = nan - arr[-3:] = nan - - return arr, index - - -def _test_data2(): - # nan-based - arr = np.arange(15, dtype=float) - index = np.arange(15) - arr[7:12] = nan - arr[-1:] = nan - return arr, index - - -def _test_data1_zero(): - # zero-based - arr, index = _test_data1() - arr[np.isnan(arr)] = 0 - return arr, index - - -def _test_data2_zero(): - # zero-based - arr, index = _test_data2() - arr[np.isnan(arr)] = 0 - return arr, index - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") -class TestSparseSeries(SharedWithSparse): - - series_klass = SparseSeries - # SharedWithSparse tests use generic, series_klass-agnostic assertion - _assert_series_equal = staticmethod(tm.assert_sp_series_equal) - - def setup_method(self, method): - arr, index = _test_data1() - - date_index = bdate_range("1/1/2011", periods=len(index)) - - self.bseries = SparseSeries(arr, index=index, kind="block", name="bseries") - self.ts = self.bseries - - self.btseries = SparseSeries(arr, index=date_index, kind="block") - - self.iseries = SparseSeries(arr, index=index, kind="integer", name="iseries") - - arr, index = _test_data2() - self.bseries2 = SparseSeries(arr, index=index, kind="block") - self.iseries2 = SparseSeries(arr, index=index, kind="integer") - - arr, index = _test_data1_zero() - self.zbseries = SparseSeries( - arr, index=index, kind="block", fill_value=0, name="zbseries" - ) - self.ziseries = SparseSeries(arr, index=index, kind="integer", fill_value=0) - - arr, index = _test_data2_zero() - self.zbseries2 = SparseSeries(arr, index=index, kind="block", fill_value=0) - self.ziseries2 = SparseSeries(arr, index=index, kind="integer", fill_value=0) - - def test_constructor_dict_input(self): - # gh-16905 - constructor_dict = {1: 1.0} - index = [0, 1, 2] - - # Series with index passed in - series = pd.Series(constructor_dict) - expected = SparseSeries(series, index=index) - - result = SparseSeries(constructor_dict, index=index) - tm.assert_sp_series_equal(result, expected) - - # Series with index and dictionary with no index - expected = SparseSeries(series) - - result = SparseSeries(constructor_dict) - tm.assert_sp_series_equal(result, expected) - - def test_constructor_dict_order(self): - # GH19018 - # initialization ordering: by insertion order if python>= 3.6, else - # order by value - d = {"b": 1, "a": 0, "c": 2} - result = SparseSeries(d) - if PY36: - expected = SparseSeries([1, 0, 2], index=list("bac")) - else: - expected = SparseSeries([0, 1, 2], index=list("abc")) - tm.assert_sp_series_equal(result, expected) - - def test_constructor_dtype(self): - arr = SparseSeries([np.nan, 1, 2, np.nan]) - assert arr.dtype == SparseDtype(np.float64) - assert np.isnan(arr.fill_value) - - arr = SparseSeries([np.nan, 1, 2, np.nan], fill_value=0) - assert arr.dtype == SparseDtype(np.float64, 0) - assert arr.fill_value == 0 - - arr = SparseSeries([0, 1, 2, 4], dtype=np.int64, fill_value=np.nan) - assert arr.dtype == SparseDtype(np.int64, np.nan) - assert np.isnan(arr.fill_value) - - arr = SparseSeries([0, 1, 2, 4], dtype=np.int64) - assert arr.dtype == SparseDtype(np.int64, 0) - assert arr.fill_value == 0 - - arr = SparseSeries([0, 1, 2, 4], fill_value=0, dtype=np.int64) - assert arr.dtype == SparseDtype(np.int64, 0) - assert arr.fill_value == 0 - - def test_iteration_and_str(self): - [x for x in self.bseries] - str(self.bseries) - - def test_construct_DataFrame_with_sp_series(self): - # it works! - df = DataFrame({"col": self.bseries}) - - # printing & access - df.iloc[:1] - df["col"] - df.dtypes - str(df) - - # blocking - expected = Series({"col": "float64:sparse"}) - - # GH 26705 - Assert .ftypes is deprecated - with tm.assert_produces_warning(FutureWarning): - result = df.ftypes - tm.assert_series_equal(expected, result) - - def test_constructor_preserve_attr(self): - arr = pd.SparseArray([1, 0, 3, 0], dtype=np.int64, fill_value=0) - assert arr.dtype == SparseDtype(np.int64) - assert arr.fill_value == 0 - - s = pd.SparseSeries(arr, name="x") - assert s.dtype == SparseDtype(np.int64) - assert s.fill_value == 0 - - def test_series_density(self): - # GH2803 - ts = Series(np.random.randn(10)) - ts[2:-2] = nan - sts = ts.to_sparse() - density = sts.density # don't die - assert density == 4 / 10.0 - - def test_sparse_to_dense(self): - arr, index = _test_data1() - series = self.bseries.to_dense() - tm.assert_series_equal(series, Series(arr, name="bseries")) - - series = self.iseries.to_dense() - tm.assert_series_equal(series, Series(arr, name="iseries")) - - arr, index = _test_data1_zero() - series = self.zbseries.to_dense() - tm.assert_series_equal(series, Series(arr, name="zbseries")) - - series = self.ziseries.to_dense() - tm.assert_series_equal(series, Series(arr)) - - def test_to_dense_fill_value(self): - s = pd.Series([1, np.nan, np.nan, 3, np.nan]) - res = SparseSeries(s).to_dense() - tm.assert_series_equal(res, s) - - res = SparseSeries(s, fill_value=0).to_dense() - tm.assert_series_equal(res, s) - - s = pd.Series([1, np.nan, 0, 3, 0]) - res = SparseSeries(s, fill_value=0).to_dense() - tm.assert_series_equal(res, s) - - res = SparseSeries(s, fill_value=0).to_dense() - tm.assert_series_equal(res, s) - - s = pd.Series([np.nan, np.nan, np.nan, np.nan, np.nan]) - res = SparseSeries(s).to_dense() - tm.assert_series_equal(res, s) - - s = pd.Series([np.nan, np.nan, np.nan, np.nan, np.nan]) - res = SparseSeries(s, fill_value=0).to_dense() - tm.assert_series_equal(res, s) - - def test_dense_to_sparse(self): - series = self.bseries.to_dense() - bseries = series.to_sparse(kind="block") - iseries = series.to_sparse(kind="integer") - tm.assert_sp_series_equal(bseries, self.bseries) - tm.assert_sp_series_equal(iseries, self.iseries, check_names=False) - assert iseries.name == self.bseries.name - - assert len(series) == len(bseries) - assert len(series) == len(iseries) - assert series.shape == bseries.shape - assert series.shape == iseries.shape - - # non-NaN fill value - series = self.zbseries.to_dense() - zbseries = series.to_sparse(kind="block", fill_value=0) - ziseries = series.to_sparse(kind="integer", fill_value=0) - tm.assert_sp_series_equal(zbseries, self.zbseries) - tm.assert_sp_series_equal(ziseries, self.ziseries, check_names=False) - assert ziseries.name == self.zbseries.name - - assert len(series) == len(zbseries) - assert len(series) == len(ziseries) - assert series.shape == zbseries.shape - assert series.shape == ziseries.shape - - def test_to_dense_preserve_name(self): - assert self.bseries.name is not None - result = self.bseries.to_dense() - assert result.name == self.bseries.name - - def test_constructor(self): - # test setup guys - assert np.isnan(self.bseries.fill_value) - assert isinstance(self.bseries.sp_index, BlockIndex) - assert np.isnan(self.iseries.fill_value) - assert isinstance(self.iseries.sp_index, IntIndex) - - assert self.zbseries.fill_value == 0 - tm.assert_numpy_array_equal( - self.zbseries.values.to_dense(), self.bseries.to_dense().fillna(0).values - ) - - # pass SparseSeries - def _check_const(sparse, name): - # use passed series name - result = SparseSeries(sparse) - tm.assert_sp_series_equal(result, sparse) - assert sparse.name == name - assert result.name == name - - # use passed name - result = SparseSeries(sparse, name="x") - tm.assert_sp_series_equal(result, sparse, check_names=False) - assert result.name == "x" - - _check_const(self.bseries, "bseries") - _check_const(self.iseries, "iseries") - _check_const(self.zbseries, "zbseries") - - # Sparse time series works - date_index = bdate_range("1/1/2000", periods=len(self.bseries)) - s5 = SparseSeries(self.bseries, index=date_index) - assert isinstance(s5, SparseSeries) - - # pass Series - bseries2 = SparseSeries(self.bseries.to_dense()) - tm.assert_numpy_array_equal(self.bseries.sp_values, bseries2.sp_values) - - # pass dict? - - # don't copy the data by default - values = np.ones(self.bseries.npoints) - sp = SparseSeries(values, sparse_index=self.bseries.sp_index) - sp.sp_values[:5] = 97 - assert values[0] == 97 - - assert len(sp) == 20 - assert sp.shape == (20,) - - # but can make it copy! - sp = SparseSeries(values, sparse_index=self.bseries.sp_index, copy=True) - sp.sp_values[:5] = 100 - assert values[0] == 97 - - assert len(sp) == 20 - assert sp.shape == (20,) - - def test_constructor_scalar(self): - data = 5 - sp = SparseSeries(data, np.arange(100)) - sp = sp.reindex(np.arange(200)) - assert (sp.loc[:99] == data).all() - assert isna(sp.loc[100:]).all() - - data = np.nan - sp = SparseSeries(data, np.arange(100)) - assert len(sp) == 100 - assert sp.shape == (100,) - - def test_constructor_ndarray(self): - pass - - def test_constructor_nonnan(self): - arr = [0, 0, 0, nan, nan] - sp_series = SparseSeries(arr, fill_value=0) - tm.assert_numpy_array_equal(sp_series.values.to_dense(), np.array(arr)) - assert len(sp_series) == 5 - assert sp_series.shape == (5,) - - def test_constructor_empty(self): - # see gh-9272 - sp = SparseSeries() - assert len(sp.index) == 0 - assert sp.shape == (0,) - - def test_copy_astype(self): - cop = self.bseries.astype(np.float64) - assert cop is not self.bseries - assert cop.sp_index is self.bseries.sp_index - assert cop.dtype == SparseDtype(np.float64) - - cop2 = self.iseries.copy() - - tm.assert_sp_series_equal(cop, self.bseries) - tm.assert_sp_series_equal(cop2, self.iseries) - - # test that data is copied - cop[:5] = 97 - assert cop.sp_values[0] == 97 - assert self.bseries.sp_values[0] != 97 - - # correct fill value - zbcop = self.zbseries.copy() - zicop = self.ziseries.copy() - - tm.assert_sp_series_equal(zbcop, self.zbseries) - tm.assert_sp_series_equal(zicop, self.ziseries) - - # no deep copy - view = self.bseries.copy(deep=False) - view.sp_values[:5] = 5 - assert (self.bseries.sp_values[:5] == 5).all() - - def test_shape(self): - # see gh-10452 - assert self.bseries.shape == (20,) - assert self.btseries.shape == (20,) - assert self.iseries.shape == (20,) - - assert self.bseries2.shape == (15,) - assert self.iseries2.shape == (15,) - - assert self.zbseries2.shape == (15,) - assert self.ziseries2.shape == (15,) - - def test_astype(self): - result = self.bseries.astype(SparseDtype(np.int64, 0)) - expected = ( - self.bseries.to_dense().fillna(0).astype(np.int64).to_sparse(fill_value=0) - ) - tm.assert_sp_series_equal(result, expected) - - def test_astype_all(self): - orig = pd.Series(np.array([1, 2, 3])) - s = SparseSeries(orig) - - types = [np.float64, np.float32, np.int64, np.int32, np.int16, np.int8] - for typ in types: - dtype = SparseDtype(typ) - res = s.astype(dtype) - assert res.dtype == dtype - tm.assert_series_equal(res.to_dense(), orig.astype(typ)) - - def test_kind(self): - assert self.bseries.kind == "block" - assert self.iseries.kind == "integer" - - def test_to_frame(self): - # GH 9850 - s = pd.SparseSeries([1, 2, 0, nan, 4, nan, 0], name="x") - exp = pd.SparseDataFrame({"x": [1, 2, 0, nan, 4, nan, 0]}) - tm.assert_sp_frame_equal(s.to_frame(), exp) - - exp = pd.SparseDataFrame({"y": [1, 2, 0, nan, 4, nan, 0]}) - tm.assert_sp_frame_equal(s.to_frame(name="y"), exp) - - s = pd.SparseSeries([1, 2, 0, nan, 4, nan, 0], name="x", fill_value=0) - exp = pd.SparseDataFrame({"x": [1, 2, 0, nan, 4, nan, 0]}, default_fill_value=0) - - tm.assert_sp_frame_equal(s.to_frame(), exp) - exp = pd.DataFrame({"y": [1, 2, 0, nan, 4, nan, 0]}) - tm.assert_frame_equal(s.to_frame(name="y").to_dense(), exp) - - def test_pickle(self): - def _test_roundtrip(series): - unpickled = tm.round_trip_pickle(series) - tm.assert_sp_series_equal(series, unpickled) - tm.assert_series_equal(series.to_dense(), unpickled.to_dense()) - - self._check_all(_test_roundtrip) - - def _check_all(self, check_func): - check_func(self.bseries) - check_func(self.iseries) - check_func(self.zbseries) - check_func(self.ziseries) - - def test_getitem(self): - def _check_getitem(sp, dense): - for idx, val in dense.items(): - tm.assert_almost_equal(val, sp[idx]) - - for i in range(len(dense)): - tm.assert_almost_equal(sp[i], dense[i]) - # j = np.float64(i) - # assert_almost_equal(sp[j], dense[j]) - - # API change 1/6/2012 - # negative getitem works - # for i in xrange(len(dense)): - # assert_almost_equal(sp[-i], dense[-i]) - - _check_getitem(self.bseries, self.bseries.to_dense()) - _check_getitem(self.btseries, self.btseries.to_dense()) - - _check_getitem(self.zbseries, self.zbseries.to_dense()) - _check_getitem(self.iseries, self.iseries.to_dense()) - _check_getitem(self.ziseries, self.ziseries.to_dense()) - - # exception handling - with pytest.raises(IndexError, match="Out of bounds access"): - self.bseries[len(self.bseries) + 1] - - # index not contained - msg = r"Timestamp\('2011-01-31 00:00:00', freq='B'\)" - with pytest.raises(KeyError, match=msg): - self.btseries[self.btseries.index[-1] + BDay()] - - def test_get_get_value(self): - tm.assert_almost_equal(self.bseries.get(10), self.bseries[10]) - assert self.bseries.get(len(self.bseries) + 1) is None - - dt = self.btseries.index[10] - result = self.btseries.get(dt) - expected = self.btseries.to_dense()[dt] - tm.assert_almost_equal(result, expected) - - tm.assert_almost_equal(self.bseries._get_value(10), self.bseries[10]) - - def test_set_value(self): - - idx = self.btseries.index[7] - self.btseries._set_value(idx, 0) - assert self.btseries[idx] == 0 - - self.iseries._set_value("foobar", 0) - assert self.iseries.index[-1] == "foobar" - assert self.iseries["foobar"] == 0 - - def test_getitem_slice(self): - idx = self.bseries.index - res = self.bseries[::2] - assert isinstance(res, SparseSeries) - - expected = self.bseries.reindex(idx[::2]) - tm.assert_sp_series_equal(res, expected) - - res = self.bseries[:5] - assert isinstance(res, SparseSeries) - tm.assert_sp_series_equal(res, self.bseries.reindex(idx[:5])) - - res = self.bseries[5:] - tm.assert_sp_series_equal(res, self.bseries.reindex(idx[5:])) - - # negative indices - res = self.bseries[:-3] - tm.assert_sp_series_equal(res, self.bseries.reindex(idx[:-3])) - - def test_take(self): - def _compare_with_dense(sp): - dense = sp.to_dense() - - def _compare(idx): - dense_result = dense.take(idx).values - sparse_result = sp.take(idx) - assert isinstance(sparse_result, SparseSeries) - tm.assert_almost_equal(dense_result, sparse_result.values.to_dense()) - - _compare([1.0, 2.0, 3.0, 4.0, 5.0, 0.0]) - _compare([7, 2, 9, 0, 4]) - _compare([3, 6, 3, 4, 7]) - - self._check_all(_compare_with_dense) - - msg = "index 21 is out of bounds for size 20" - with pytest.raises(IndexError, match=msg): - self.bseries.take([0, len(self.bseries) + 1]) - - # Corner case - # XXX: changed test. Why wsa this considered a corner case? - sp = SparseSeries(np.ones(10) * nan) - exp = pd.Series(np.repeat(nan, 5)) - tm.assert_series_equal(sp.take([0, 1, 2, 3, 4]), exp.to_sparse()) - - def test_numpy_take(self): - sp = SparseSeries([1.0, 2.0, 3.0]) - indices = [1, 2] - - tm.assert_series_equal( - np.take(sp, indices, axis=0).to_dense(), - np.take(sp.to_dense(), indices, axis=0), - ) - - msg = "the 'out' parameter is not supported" - with pytest.raises(ValueError, match=msg): - np.take(sp, indices, out=np.empty(sp.shape)) - - msg = "the 'mode' parameter is not supported" - with pytest.raises(ValueError, match=msg): - np.take(sp, indices, out=None, mode="clip") - - def test_setitem(self): - self.bseries[5] = 7.0 - assert self.bseries[5] == 7.0 - - def test_setslice(self): - self.bseries[5:10] = 7.0 - tm.assert_series_equal( - self.bseries[5:10].to_dense(), - Series(7.0, index=range(5, 10), name=self.bseries.name), - ) - - def test_operators(self): - def _check_op(a, b, op): - sp_result = op(a, b) - adense = a.to_dense() if isinstance(a, SparseSeries) else a - bdense = b.to_dense() if isinstance(b, SparseSeries) else b - dense_result = op(adense, bdense) - if "floordiv" in op.__name__: - # Series sets 1//0 to np.inf, which SparseSeries does not do (yet) - mask = np.isinf(dense_result) - dense_result[mask] = np.nan - tm.assert_almost_equal(sp_result.to_dense(), dense_result) - - def check(a, b): - _check_op(a, b, operator.add) - _check_op(a, b, operator.sub) - _check_op(a, b, operator.truediv) - _check_op(a, b, operator.floordiv) - _check_op(a, b, operator.mul) - - _check_op(a, b, ops.radd) - _check_op(a, b, ops.rsub) - _check_op(a, b, ops.rtruediv) - _check_op(a, b, ops.rfloordiv) - _check_op(a, b, ops.rmul) - - # FIXME: don't leave commented-out - # NaN ** 0 = 1 in C? - # _check_op(a, b, operator.pow) - # _check_op(a, b, ops.rpow) - - check(self.bseries, self.bseries) - check(self.iseries, self.iseries) - check(self.bseries, self.iseries) - - check(self.bseries, self.bseries2) - check(self.bseries, self.iseries2) - check(self.iseries, self.iseries2) - - # scalar value - check(self.bseries, 5) - - # zero-based - check(self.zbseries, self.zbseries * 2) - check(self.zbseries, self.zbseries2) - check(self.ziseries, self.ziseries2) - - # with dense - result = self.bseries + self.bseries.to_dense() - tm.assert_sp_series_equal(result, self.bseries + self.bseries) - - def test_binary_operators(self): - - # skipping for now ##### - import pytest - - pytest.skip("skipping sparse binary operators test") - - def _check_inplace_op(iop, op): - tmp = self.bseries.copy() - - expected = op(tmp, self.bseries) - iop(tmp, self.bseries) - tm.assert_sp_series_equal(tmp, expected) - - inplace_ops = ["add", "sub", "mul", "truediv", "floordiv", "pow"] - for op in inplace_ops: - _check_inplace_op( - getattr(operator, "i{op}".format(op=op)), getattr(operator, op) - ) - - @pytest.mark.parametrize( - "values, op, fill_value", - [ - ([True, False, False, True], operator.invert, True), - ([True, False, False, True], operator.invert, False), - ([0, 1, 2, 3], operator.pos, 0), - ([0, 1, 2, 3], operator.neg, 0), - ([0, np.nan, 2, 3], operator.pos, np.nan), - ([0, np.nan, 2, 3], operator.neg, np.nan), - ], - ) - def test_unary_operators(self, values, op, fill_value): - # https://github.com/pandas-dev/pandas/issues/22835 - values = np.asarray(values) - if op is operator.invert: - new_fill_value = not fill_value - else: - new_fill_value = op(fill_value) - s = SparseSeries( - values, fill_value=fill_value, index=["a", "b", "c", "d"], name="name" - ) - result = op(s) - expected = SparseSeries( - op(values), - fill_value=new_fill_value, - index=["a", "b", "c", "d"], - name="name", - ) - tm.assert_sp_series_equal(result, expected) - - def test_abs(self): - s = SparseSeries([1, 2, -3], name="x") - expected = SparseSeries([1, 2, 3], name="x") - result = s.abs() - tm.assert_sp_series_equal(result, expected) - assert result.name == "x" - - result = abs(s) - tm.assert_sp_series_equal(result, expected) - assert result.name == "x" - - result = np.abs(s) - tm.assert_sp_series_equal(result, expected) - assert result.name == "x" - - s = SparseSeries([1, -2, 2, -3], fill_value=-2, name="x") - expected = SparseSeries( - [1, 2, 3], sparse_index=s.sp_index, fill_value=2, name="x" - ) - result = s.abs() - tm.assert_sp_series_equal(result, expected) - assert result.name == "x" - - result = abs(s) - tm.assert_sp_series_equal(result, expected) - assert result.name == "x" - - result = np.abs(s) - tm.assert_sp_series_equal(result, expected) - assert result.name == "x" - - def test_reindex(self): - def _compare_with_series(sps, new_index): - spsre = sps.reindex(new_index) - - series = sps.to_dense() - seriesre = series.reindex(new_index) - seriesre = seriesre.to_sparse(fill_value=sps.fill_value) - - tm.assert_sp_series_equal(spsre, seriesre) - tm.assert_series_equal(spsre.to_dense(), seriesre.to_dense()) - - _compare_with_series(self.bseries, self.bseries.index[::2]) - _compare_with_series(self.bseries, list(self.bseries.index[::2])) - _compare_with_series(self.bseries, self.bseries.index[:10]) - _compare_with_series(self.bseries, self.bseries.index[5:]) - - _compare_with_series(self.zbseries, self.zbseries.index[::2]) - _compare_with_series(self.zbseries, self.zbseries.index[:10]) - _compare_with_series(self.zbseries, self.zbseries.index[5:]) - - # special cases - same_index = self.bseries.reindex(self.bseries.index) - tm.assert_sp_series_equal(self.bseries, same_index) - assert same_index is not self.bseries - - # corner cases - sp = SparseSeries([], index=[]) - # TODO: sp_zero is not used anywhere...remove? - sp_zero = SparseSeries([], index=[], fill_value=0) # noqa - _compare_with_series(sp, np.arange(10)) - - # with copy=False - reindexed = self.bseries.reindex(self.bseries.index, copy=True) - reindexed.sp_values[:] = 1.0 - assert (self.bseries.sp_values != 1.0).all() - - reindexed = self.bseries.reindex(self.bseries.index, copy=False) - reindexed.sp_values[:] = 1.0 - tm.assert_numpy_array_equal(self.bseries.sp_values, np.repeat(1.0, 10)) - - def test_sparse_reindex(self): - length = 10 - - def _check(values, index1, index2, fill_value): - first_series = SparseSeries( - values, sparse_index=index1, fill_value=fill_value - ) - reindexed = first_series.sparse_reindex(index2) - assert reindexed.sp_index is index2 - - int_indices1 = index1.to_int_index().indices - int_indices2 = index2.to_int_index().indices - - expected = Series(values, index=int_indices1) - expected = expected.reindex(int_indices2).fillna(fill_value) - tm.assert_almost_equal(expected.values, reindexed.sp_values) - - # make sure level argument asserts - # TODO: expected is not used anywhere...remove? - expected = expected.reindex(int_indices2).fillna(fill_value) # noqa - - def _check_with_fill_value(values, first, second, fill_value=nan): - i_index1 = IntIndex(length, first) - i_index2 = IntIndex(length, second) - - b_index1 = i_index1.to_block_index() - b_index2 = i_index2.to_block_index() - - _check(values, i_index1, i_index2, fill_value) - _check(values, b_index1, b_index2, fill_value) - - def _check_all(values, first, second): - _check_with_fill_value(values, first, second, fill_value=nan) - _check_with_fill_value(values, first, second, fill_value=0) - - index1 = [2, 4, 5, 6, 8, 9] - values1 = np.arange(6.0) - - _check_all(values1, index1, [2, 4, 5]) - _check_all(values1, index1, [2, 3, 4, 5, 6, 7, 8, 9]) - _check_all(values1, index1, [0, 1]) - _check_all(values1, index1, [0, 1, 7, 8, 9]) - _check_all(values1, index1, []) - - first_series = SparseSeries( - values1, sparse_index=IntIndex(length, index1), fill_value=nan - ) - with pytest.raises(TypeError, match="new index must be a SparseIndex"): - first_series.sparse_reindex(0) - - def test_repr(self): - # TODO: These aren't used - bsrepr = repr(self.bseries) # noqa - isrepr = repr(self.iseries) # noqa - - def test_iter(self): - pass - - def test_truncate(self): - pass - - def test_fillna(self): - pass - - def test_groupby(self): - pass - - def test_reductions(self): - def _compare_with_dense(obj, op): - sparse_result = getattr(obj, op)() - series = obj.to_dense() - dense_result = getattr(series, op)() - assert sparse_result == dense_result - - to_compare = ["count", "sum", "mean", "std", "var", "skew"] - - def _compare_all(obj): - for op in to_compare: - _compare_with_dense(obj, op) - - _compare_all(self.bseries) - - self.bseries.sp_values[5:10] = np.NaN - _compare_all(self.bseries) - - _compare_all(self.zbseries) - self.zbseries.sp_values[5:10] = np.NaN - _compare_all(self.zbseries) - - series = self.zbseries.copy() - series.fill_value = 2 - _compare_all(series) - - nonna = Series(np.random.randn(20)).to_sparse() - _compare_all(nonna) - - nonna2 = Series(np.random.randn(20)).to_sparse(fill_value=0) - _compare_all(nonna2) - - def test_dropna(self): - sp = SparseSeries([0, 0, 0, nan, nan, 5, 6], fill_value=0) - - sp_valid = sp.dropna() - - expected = sp.to_dense().dropna() - expected = expected[expected != 0] - exp_arr = pd.SparseArray(expected.values, fill_value=0, kind="block") - tm.assert_sp_array_equal(sp_valid.values, exp_arr) - tm.assert_index_equal(sp_valid.index, expected.index) - assert len(sp_valid.sp_values) == 2 - - result = self.bseries.dropna() - expected = self.bseries.to_dense().dropna() - assert not isinstance(result, SparseSeries) - tm.assert_series_equal(result, expected) - - def test_homogenize(self): - def _check_matches(indices, expected): - data = { - i: SparseSeries( - idx.to_int_index().indices, sparse_index=idx, fill_value=np.nan - ) - for i, idx in enumerate(indices) - } - - # homogenized is only valid with NaN fill values - homogenized = spf.homogenize(data) - - for k, v in homogenized.items(): - assert v.sp_index.equals(expected) - - indices1 = [ - BlockIndex(10, [2], [7]), - BlockIndex(10, [1, 6], [3, 4]), - BlockIndex(10, [0], [10]), - ] - expected1 = BlockIndex(10, [2, 6], [2, 3]) - _check_matches(indices1, expected1) - - indices2 = [BlockIndex(10, [2], [7]), BlockIndex(10, [2], [7])] - expected2 = indices2[0] - _check_matches(indices2, expected2) - - # must have NaN fill value - data = {"a": SparseSeries(np.arange(7), sparse_index=expected2, fill_value=0)} - with pytest.raises(TypeError, match="NaN fill value"): - spf.homogenize(data) - - def test_fill_value_corner(self): - cop = self.zbseries.copy() - cop.fill_value = 0 - result = self.bseries / cop - - assert np.isnan(result.fill_value) - - cop2 = self.zbseries.copy() - cop2.fill_value = 1 - result = cop2 / cop - # 1 / 0 is inf - assert np.isinf(result.fill_value) - - def test_fill_value_when_combine_const(self): - # GH12723 - s = SparseSeries([0, 1, np.nan, 3, 4, 5], index=np.arange(6)) - - exp = s.fillna(0).add(2) - res = s.add(2, fill_value=0) - tm.assert_series_equal(res, exp) - - def test_shift(self): - series = SparseSeries([nan, 1.0, 2.0, 3.0, nan, nan], index=np.arange(6)) - - shifted = series.shift(0) - # assert shifted is not series - tm.assert_sp_series_equal(shifted, series) - - f = lambda s: s.shift(1) - _dense_series_compare(series, f) - - f = lambda s: s.shift(-2) - _dense_series_compare(series, f) - - series = SparseSeries( - [nan, 1.0, 2.0, 3.0, nan, nan], index=bdate_range("1/1/2000", periods=6) - ) - f = lambda s: s.shift(2, freq="B") - _dense_series_compare(series, f) - - f = lambda s: s.shift(2, freq=BDay()) - _dense_series_compare(series, f) - - def test_shift_nan(self): - # GH 12908 - orig = pd.Series([np.nan, 2, np.nan, 4, 0, np.nan, 0]) - sparse = orig.to_sparse() - - tm.assert_sp_series_equal( - sparse.shift(0), orig.shift(0).to_sparse(), check_kind=False - ) - tm.assert_sp_series_equal( - sparse.shift(1), orig.shift(1).to_sparse(), check_kind=False - ) - tm.assert_sp_series_equal( - sparse.shift(2), orig.shift(2).to_sparse(), check_kind=False - ) - tm.assert_sp_series_equal( - sparse.shift(3), orig.shift(3).to_sparse(), check_kind=False - ) - - tm.assert_sp_series_equal(sparse.shift(-1), orig.shift(-1).to_sparse()) - tm.assert_sp_series_equal(sparse.shift(-2), orig.shift(-2).to_sparse()) - tm.assert_sp_series_equal(sparse.shift(-3), orig.shift(-3).to_sparse()) - tm.assert_sp_series_equal(sparse.shift(-4), orig.shift(-4).to_sparse()) - - sparse = orig.to_sparse(fill_value=0) - tm.assert_sp_series_equal( - sparse.shift(0), orig.shift(0).to_sparse(fill_value=sparse.fill_value) - ) - tm.assert_sp_series_equal( - sparse.shift(1), orig.shift(1).to_sparse(fill_value=0), check_kind=False - ) - tm.assert_sp_series_equal( - sparse.shift(2), orig.shift(2).to_sparse(fill_value=0), check_kind=False - ) - tm.assert_sp_series_equal( - sparse.shift(3), orig.shift(3).to_sparse(fill_value=0), check_kind=False - ) - - tm.assert_sp_series_equal( - sparse.shift(-1), orig.shift(-1).to_sparse(fill_value=0), check_kind=False - ) - tm.assert_sp_series_equal( - sparse.shift(-2), orig.shift(-2).to_sparse(fill_value=0), check_kind=False - ) - tm.assert_sp_series_equal( - sparse.shift(-3), orig.shift(-3).to_sparse(fill_value=0), check_kind=False - ) - tm.assert_sp_series_equal( - sparse.shift(-4), orig.shift(-4).to_sparse(fill_value=0), check_kind=False - ) - - def test_shift_dtype(self): - # GH 12908 - orig = pd.Series([1, 2, 3, 4], dtype=np.int64) - - sparse = orig.to_sparse() - tm.assert_sp_series_equal(sparse.shift(0), orig.shift(0).to_sparse()) - - sparse = orig.to_sparse(fill_value=np.nan) - tm.assert_sp_series_equal( - sparse.shift(0), orig.shift(0).to_sparse(fill_value=np.nan) - ) - # shift(1) or more span changes dtype to float64 - # XXX: SparseSeries doesn't need to shift dtype here. - # Do we want to astype in shift, for backwards compat? - # If not, document it. - tm.assert_sp_series_equal( - sparse.shift(1).astype("f8"), orig.shift(1).to_sparse(kind="integer") - ) - tm.assert_sp_series_equal( - sparse.shift(2).astype("f8"), orig.shift(2).to_sparse(kind="integer") - ) - tm.assert_sp_series_equal( - sparse.shift(3).astype("f8"), orig.shift(3).to_sparse(kind="integer") - ) - - tm.assert_sp_series_equal( - sparse.shift(-1).astype("f8"), orig.shift(-1).to_sparse(), check_kind=False - ) - tm.assert_sp_series_equal( - sparse.shift(-2).astype("f8"), orig.shift(-2).to_sparse(), check_kind=False - ) - tm.assert_sp_series_equal( - sparse.shift(-3).astype("f8"), orig.shift(-3).to_sparse(), check_kind=False - ) - tm.assert_sp_series_equal( - sparse.shift(-4).astype("f8"), orig.shift(-4).to_sparse(), check_kind=False - ) - - @pytest.mark.parametrize("fill_value", [0, 1, np.nan]) - @pytest.mark.parametrize("periods", [0, 1, 2, 3, -1, -2, -3, -4]) - def test_shift_dtype_fill_value(self, fill_value, periods): - # GH 12908 - orig = pd.Series([1, 0, 0, 4], dtype=np.dtype("int64")) - - sparse = orig.to_sparse(fill_value=fill_value) - - result = sparse.shift(periods) - expected = orig.shift(periods).to_sparse(fill_value=fill_value) - - tm.assert_sp_series_equal( - result, expected, check_kind=False, consolidate_block_indices=True - ) - - def test_combine_first(self): - s = self.bseries - - result = s[::2].combine_first(s) - result2 = s[::2].combine_first(s.to_dense()) - - expected = s[::2].to_dense().combine_first(s.to_dense()) - expected = expected.to_sparse(fill_value=s.fill_value) - - tm.assert_sp_series_equal(result, result2) - tm.assert_sp_series_equal(result, expected) - - @pytest.mark.parametrize("deep", [True, False]) - @pytest.mark.parametrize("fill_value", [0, 1, np.nan, None]) - def test_memory_usage_deep(self, deep, fill_value): - values = [1.0] + [fill_value] * 20 - sparse_series = SparseSeries(values, fill_value=fill_value) - dense_series = Series(values) - sparse_usage = sparse_series.memory_usage(deep=deep) - dense_usage = dense_series.memory_usage(deep=deep) - - assert sparse_usage < dense_usage - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:DataFrame.to_sparse:FutureWarning") -class TestSparseHandlingMultiIndexes: - def setup_method(self, method): - miindex = pd.MultiIndex.from_product( - [["x", "y"], ["10", "20"]], names=["row-foo", "row-bar"] - ) - micol = pd.MultiIndex.from_product( - [["a", "b", "c"], ["1", "2"]], names=["col-foo", "col-bar"] - ) - dense_multiindex_frame = ( - pd.DataFrame(index=miindex, columns=micol).sort_index().sort_index(axis=1) - ) - self.dense_multiindex_frame = dense_multiindex_frame.fillna(value=3.14) - - def test_to_sparse_preserve_multiindex_names_columns(self): - sparse_multiindex_frame = self.dense_multiindex_frame.to_sparse() - sparse_multiindex_frame = sparse_multiindex_frame.copy() - tm.assert_index_equal( - sparse_multiindex_frame.columns, self.dense_multiindex_frame.columns - ) - - def test_round_trip_preserve_multiindex_names(self): - sparse_multiindex_frame = self.dense_multiindex_frame.to_sparse() - round_trip_multiindex_frame = sparse_multiindex_frame.to_dense() - tm.assert_frame_equal( - self.dense_multiindex_frame, - round_trip_multiindex_frame, - check_column_type=True, - check_names=True, - ) - - -@td.skip_if_no_scipy -@pytest.mark.filterwarnings("ignore:the matrix subclass:PendingDeprecationWarning") -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") -class TestSparseSeriesScipyInteraction: - # Issue 8048: add SparseSeries coo methods - - def setup_method(self, method): - import scipy.sparse - - # SparseSeries inputs used in tests, the tests rely on the order - self.sparse_series = [] - s = pd.Series([3.0, nan, 1.0, 2.0, nan, nan]) - s.index = pd.MultiIndex.from_tuples( - [ - (1, 2, "a", 0), - (1, 2, "a", 1), - (1, 1, "b", 0), - (1, 1, "b", 1), - (2, 1, "b", 0), - (2, 1, "b", 1), - ], - names=["A", "B", "C", "D"], - ) - self.sparse_series.append(s.to_sparse()) - - ss = self.sparse_series[0].copy() - ss.index.names = [3, 0, 1, 2] - self.sparse_series.append(ss) - - ss = pd.Series( - [nan] * 12, index=cartesian_product((range(3), range(4))) - ).to_sparse() - for k, v in zip([(0, 0), (1, 2), (1, 3)], [3.0, 1.0, 2.0]): - ss[k] = v - self.sparse_series.append(ss) - - # results used in tests - self.coo_matrices = [] - self.coo_matrices.append( - scipy.sparse.coo_matrix( - ([3.0, 1.0, 2.0], ([0, 1, 1], [0, 2, 3])), shape=(3, 4) - ) - ) - self.coo_matrices.append( - scipy.sparse.coo_matrix( - ([3.0, 1.0, 2.0], ([1, 0, 0], [0, 2, 3])), shape=(3, 4) - ) - ) - self.coo_matrices.append( - scipy.sparse.coo_matrix( - ([3.0, 1.0, 2.0], ([0, 1, 1], [0, 0, 1])), shape=(3, 2) - ) - ) - self.ils = [ - [(1, 2), (1, 1), (2, 1)], - [(1, 1), (1, 2), (2, 1)], - [(1, 2, "a"), (1, 1, "b"), (2, 1, "b")], - ] - self.jls = [[("a", 0), ("a", 1), ("b", 0), ("b", 1)], [0, 1]] - - def test_to_coo_text_names_integer_row_levels_nosort(self): - ss = self.sparse_series[0] - kwargs = {"row_levels": [0, 1], "column_levels": [2, 3]} - result = (self.coo_matrices[0], self.ils[0], self.jls[0]) - self._run_test(ss, kwargs, result) - - def test_to_coo_text_names_integer_row_levels_sort(self): - ss = self.sparse_series[0] - kwargs = {"row_levels": [0, 1], "column_levels": [2, 3], "sort_labels": True} - result = (self.coo_matrices[1], self.ils[1], self.jls[0]) - self._run_test(ss, kwargs, result) - - def test_to_coo_text_names_text_row_levels_nosort_col_level_single(self): - ss = self.sparse_series[0] - kwargs = { - "row_levels": ["A", "B", "C"], - "column_levels": ["D"], - "sort_labels": False, - } - result = (self.coo_matrices[2], self.ils[2], self.jls[1]) - self._run_test(ss, kwargs, result) - - def test_to_coo_integer_names_integer_row_levels_nosort(self): - ss = self.sparse_series[1] - kwargs = {"row_levels": [3, 0], "column_levels": [1, 2]} - result = (self.coo_matrices[0], self.ils[0], self.jls[0]) - self._run_test(ss, kwargs, result) - - def test_to_coo_text_names_text_row_levels_nosort(self): - ss = self.sparse_series[0] - kwargs = {"row_levels": ["A", "B"], "column_levels": ["C", "D"]} - result = (self.coo_matrices[0], self.ils[0], self.jls[0]) - self._run_test(ss, kwargs, result) - - def test_to_coo_bad_partition_nonnull_intersection(self): - ss = self.sparse_series[0] - msg = "Is not a partition because intersection is not null" - with pytest.raises(ValueError, match=msg): - ss.to_coo(["A", "B", "C"], ["C", "D"]) - - def test_to_coo_bad_partition_small_union(self): - ss = self.sparse_series[0] - msg = "Is not a partition because union is not the whole" - with pytest.raises(ValueError, match=msg): - ss.to_coo(["A"], ["C", "D"]) - - def test_to_coo_nlevels_less_than_two(self): - ss = self.sparse_series[0] - ss.index = np.arange(len(ss.index)) - msg = "to_coo requires MultiIndex with nlevels > 2" - with pytest.raises(ValueError, match=msg): - ss.to_coo() - - def test_to_coo_bad_ilevel(self): - ss = self.sparse_series[0] - with pytest.raises(KeyError, match="Level E not found"): - ss.to_coo(["A", "B"], ["C", "D", "E"]) - - def test_to_coo_duplicate_index_entries(self): - ss = pd.concat([self.sparse_series[0], self.sparse_series[0]]).to_sparse() - msg = "Duplicate index entries are not allowed in to_coo transformation" - with pytest.raises(ValueError, match=msg): - ss.to_coo(["A", "B"], ["C", "D"]) - - def test_from_coo_dense_index(self): - ss = SparseSeries.from_coo(self.coo_matrices[0], dense_index=True) - check = self.sparse_series[2] - tm.assert_sp_series_equal(ss, check) - - def test_from_coo_nodense_index(self): - ss = SparseSeries.from_coo(self.coo_matrices[0], dense_index=False) - check = self.sparse_series[2] - check = check.dropna().to_sparse() - tm.assert_sp_series_equal(ss, check) - - def test_from_coo_long_repr(self): - # GH 13114 - # test it doesn't raise error. Formatting is tested in test_format - import scipy.sparse - - sparse = SparseSeries.from_coo(scipy.sparse.rand(350, 18)) - repr(sparse) - - def _run_test(self, ss, kwargs, check): - results = ss.to_coo(**kwargs) - self._check_results_to_coo(results, check) - # for every test, also test symmetry property (transpose), switch - # row_levels and column_levels - d = kwargs.copy() - d["row_levels"] = kwargs["column_levels"] - d["column_levels"] = kwargs["row_levels"] - results = ss.to_coo(**d) - results = (results[0].T, results[2], results[1]) - self._check_results_to_coo(results, check) - - def _check_results_to_coo(self, results, check): - (A, il, jl) = results - (A_result, il_result, jl_result) = check - # convert to dense and compare - tm.assert_numpy_array_equal(A.todense(), A_result.todense()) - # or compare directly as difference of sparse - # assert(abs(A - A_result).max() < 1e-12) # max is failing in python - # 2.6 - assert il == il_result - assert jl == jl_result - - def test_concat(self): - val1 = np.array([1, 2, np.nan, np.nan, 0, np.nan]) - val2 = np.array([3, np.nan, 4, 0, 0]) - - for kind in ["integer", "block"]: - sparse1 = pd.SparseSeries(val1, name="x", kind=kind) - sparse2 = pd.SparseSeries(val2, name="y", kind=kind) - - res = pd.concat([sparse1, sparse2]) - exp = pd.concat([pd.Series(val1), pd.Series(val2)]) - exp = pd.SparseSeries(exp, kind=kind) - tm.assert_sp_series_equal(res, exp) - - sparse1 = pd.SparseSeries(val1, fill_value=0, name="x", kind=kind) - sparse2 = pd.SparseSeries(val2, fill_value=0, name="y", kind=kind) - - res = pd.concat([sparse1, sparse2]) - exp = pd.concat([pd.Series(val1), pd.Series(val2)]) - exp = pd.SparseSeries(exp, fill_value=0, kind=kind) - tm.assert_sp_series_equal(res, exp, consolidate_block_indices=True) - - def test_concat_axis1(self): - val1 = np.array([1, 2, np.nan, np.nan, 0, np.nan]) - val2 = np.array([3, np.nan, 4, 0, 0]) - - sparse1 = pd.SparseSeries(val1, name="x") - sparse2 = pd.SparseSeries(val2, name="y") - - res = pd.concat([sparse1, sparse2], axis=1) - exp = pd.concat([pd.Series(val1, name="x"), pd.Series(val2, name="y")], axis=1) - exp = pd.SparseDataFrame(exp) - tm.assert_sp_frame_equal(res, exp) - - def test_concat_different_fill(self): - val1 = np.array([1, 2, np.nan, np.nan, 0, np.nan]) - val2 = np.array([3, np.nan, 4, 0, 0]) - - for kind in ["integer", "block"]: - sparse1 = pd.SparseSeries(val1, name="x", kind=kind) - sparse2 = pd.SparseSeries(val2, name="y", kind=kind, fill_value=0) - - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - res = pd.concat([sparse1, sparse2]) - exp = pd.concat([pd.Series(val1), pd.Series(val2)]) - exp = pd.SparseSeries(exp, kind=kind) - tm.assert_sp_series_equal(res, exp) - - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - res = pd.concat([sparse2, sparse1]) - exp = pd.concat([pd.Series(val2), pd.Series(val1)]) - exp = pd.SparseSeries(exp, kind=kind, fill_value=0) - tm.assert_sp_series_equal(res, exp) - - def test_concat_axis1_different_fill(self): - val1 = np.array([1, 2, np.nan, np.nan, 0, np.nan]) - val2 = np.array([3, np.nan, 4, 0, 0]) - - sparse1 = pd.SparseSeries(val1, name="x") - sparse2 = pd.SparseSeries(val2, name="y", fill_value=0) - - res = pd.concat([sparse1, sparse2], axis=1) - exp = pd.concat([pd.Series(val1, name="x"), pd.Series(val2, name="y")], axis=1) - assert isinstance(res, pd.SparseDataFrame) - tm.assert_frame_equal(res.to_dense(), exp) - - def test_concat_different_kind(self): - val1 = np.array([1, 2, np.nan, np.nan, 0, np.nan]) - val2 = np.array([3, np.nan, 4, 0, 0]) - - sparse1 = pd.SparseSeries(val1, name="x", kind="integer") - sparse2 = pd.SparseSeries(val2, name="y", kind="block", fill_value=0) - - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - res = pd.concat([sparse1, sparse2]) - exp = pd.concat([pd.Series(val1), pd.Series(val2)]) - exp = pd.SparseSeries(exp, kind="integer") - tm.assert_sp_series_equal(res, exp) - - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - res = pd.concat([sparse2, sparse1]) - exp = pd.concat([pd.Series(val2), pd.Series(val1)]) - exp = pd.SparseSeries(exp, kind="block", fill_value=0) - tm.assert_sp_series_equal(res, exp) - - def test_concat_sparse_dense(self): - # use first input's fill_value - val1 = np.array([1, 2, np.nan, np.nan, 0, np.nan]) - val2 = np.array([3, np.nan, 4, 0, 0]) - - for kind in ["integer", "block"]: - sparse = pd.SparseSeries(val1, name="x", kind=kind) - dense = pd.Series(val2, name="y") - - res = pd.concat([sparse, dense]) - exp = pd.concat([pd.Series(val1), dense]) - exp = pd.SparseSeries(exp, kind=kind) - tm.assert_sp_series_equal(res, exp) - - res = pd.concat([dense, sparse, dense]) - exp = pd.concat([dense, pd.Series(val1), dense]) - exp = exp.astype("Sparse") - tm.assert_series_equal(res, exp) - - sparse = pd.SparseSeries(val1, name="x", kind=kind, fill_value=0) - dense = pd.Series(val2, name="y") - - res = pd.concat([sparse, dense]) - exp = pd.concat([pd.Series(val1), dense]) - exp = exp.astype(SparseDtype(exp.dtype, 0)) - tm.assert_series_equal(res, exp) - - res = pd.concat([dense, sparse, dense]) - exp = pd.concat([dense, pd.Series(val1), dense]) - exp = exp.astype(SparseDtype(exp.dtype, 0)) - tm.assert_series_equal(res, exp) - - def test_value_counts(self): - vals = [1, 2, nan, 0, nan, 1, 2, nan, nan, 1, 2, 0, 1, 1] - dense = pd.Series(vals, name="xx") - - sparse = pd.SparseSeries(vals, name="xx") - tm.assert_series_equal(sparse.value_counts(), dense.value_counts()) - tm.assert_series_equal( - sparse.value_counts(dropna=False), dense.value_counts(dropna=False) - ) - - sparse = pd.SparseSeries(vals, name="xx", fill_value=0) - tm.assert_series_equal(sparse.value_counts(), dense.value_counts()) - tm.assert_series_equal( - sparse.value_counts(dropna=False), dense.value_counts(dropna=False) - ) - - def test_value_counts_dup(self): - vals = [1, 2, nan, 0, nan, 1, 2, nan, nan, 1, 2, 0, 1, 1] - - # numeric op may cause sp_values to include the same value as - # fill_value - dense = pd.Series(vals, name="xx") / 0.0 - sparse = pd.SparseSeries(vals, name="xx") / 0.0 - tm.assert_series_equal(sparse.value_counts(), dense.value_counts()) - tm.assert_series_equal( - sparse.value_counts(dropna=False), dense.value_counts(dropna=False) - ) - - vals = [1, 2, 0, 0, 0, 1, 2, 0, 0, 1, 2, 0, 1, 1] - - dense = pd.Series(vals, name="xx") * 0.0 - sparse = pd.SparseSeries(vals, name="xx") * 0.0 - tm.assert_series_equal(sparse.value_counts(), dense.value_counts()) - tm.assert_series_equal( - sparse.value_counts(dropna=False), dense.value_counts(dropna=False) - ) - - def test_value_counts_int(self): - vals = [1, 2, 0, 1, 2, 1, 2, 0, 1, 1] - dense = pd.Series(vals, name="xx") - - # fill_value is np.nan, but should not be included in the result - sparse = pd.SparseSeries(vals, name="xx") - tm.assert_series_equal(sparse.value_counts(), dense.value_counts()) - tm.assert_series_equal( - sparse.value_counts(dropna=False), dense.value_counts(dropna=False) - ) - - sparse = pd.SparseSeries(vals, name="xx", fill_value=0) - tm.assert_series_equal(sparse.value_counts(), dense.value_counts()) - tm.assert_series_equal( - sparse.value_counts(dropna=False), dense.value_counts(dropna=False) - ) - - def test_isna(self): - # GH 8276 - s = pd.SparseSeries([np.nan, np.nan, 1, 2, np.nan], name="xxx") - - res = s.isna() - exp = pd.SparseSeries( - [True, True, False, False, True], name="xxx", fill_value=True - ) - tm.assert_sp_series_equal(res, exp) - - # if fill_value is not nan, True can be included in sp_values - s = pd.SparseSeries([np.nan, 0.0, 1.0, 2.0, 0.0], name="xxx", fill_value=0.0) - res = s.isna() - assert isinstance(res, pd.SparseSeries) - exp = pd.Series([True, False, False, False, False], name="xxx") - tm.assert_series_equal(res.to_dense(), exp) - - def test_notna(self): - # GH 8276 - s = pd.SparseSeries([np.nan, np.nan, 1, 2, np.nan], name="xxx") - - res = s.notna() - exp = pd.SparseSeries( - [False, False, True, True, False], name="xxx", fill_value=False - ) - tm.assert_sp_series_equal(res, exp) - - # if fill_value is not nan, True can be included in sp_values - s = pd.SparseSeries([np.nan, 0.0, 1.0, 2.0, 0.0], name="xxx", fill_value=0.0) - res = s.notna() - assert isinstance(res, pd.SparseSeries) - exp = pd.Series([False, True, True, True, True], name="xxx") - tm.assert_series_equal(res.to_dense(), exp) - - -def _dense_series_compare(s, f): - result = f(s) - assert isinstance(result, SparseSeries) - dense_result = f(s.to_dense()) - tm.assert_series_equal(result.to_dense(), dense_result) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") -class TestSparseSeriesAnalytics: - def setup_method(self, method): - arr, index = _test_data1() - self.bseries = SparseSeries(arr, index=index, kind="block", name="bseries") - - arr, index = _test_data1_zero() - self.zbseries = SparseSeries( - arr, index=index, kind="block", fill_value=0, name="zbseries" - ) - - def test_cumsum(self): - result = self.bseries.cumsum() - expected = SparseSeries(self.bseries.to_dense().cumsum()) - tm.assert_sp_series_equal(result, expected) - - result = self.zbseries.cumsum() - expected = self.zbseries.to_dense().cumsum().to_sparse() - tm.assert_series_equal(result, expected) - - axis = 1 # Series is 1-D, so only axis = 0 is valid. - msg = "No axis named {axis}".format(axis=axis) - with pytest.raises(ValueError, match=msg): - self.bseries.cumsum(axis=axis) - - def test_numpy_cumsum(self): - result = np.cumsum(self.bseries) - expected = SparseSeries(self.bseries.to_dense().cumsum()) - tm.assert_sp_series_equal(result, expected) - - result = np.cumsum(self.zbseries) - expected = self.zbseries.to_dense().cumsum().to_sparse() - tm.assert_series_equal(result, expected) - - msg = "the 'dtype' parameter is not supported" - with pytest.raises(ValueError, match=msg): - np.cumsum(self.bseries, dtype=np.int64) - - msg = "the 'out' parameter is not supported" - with pytest.raises(ValueError, match=msg): - np.cumsum(self.zbseries, out=result) - - def test_numpy_func_call(self): - # no exception should be raised even though - # numpy passes in 'axis=None' or `axis=-1' - funcs = [ - "sum", - "cumsum", - "var", - "mean", - "prod", - "cumprod", - "std", - "argsort", - "min", - "max", - ] - for func in funcs: - for series in ("bseries", "zbseries"): - getattr(np, func)(getattr(self, series)) - - def test_deprecated_numpy_func_call(self): - # NOTE: These should be add to the 'test_numpy_func_call' test above - # once the behavior of argmin/argmax is corrected. - funcs = ["argmin", "argmax"] - for func in funcs: - for series in ("bseries", "zbseries"): - with tm.assert_produces_warning( - FutureWarning, check_stacklevel=False, raise_on_extra_warnings=False - ): - getattr(np, func)(getattr(self, series)) - - with tm.assert_produces_warning( - FutureWarning, check_stacklevel=False, raise_on_extra_warnings=False - ): - getattr(getattr(self, series), func)() - - -@pytest.mark.parametrize( - "datetime_type", - (np.datetime64, pd.Timestamp, lambda x: datetime.strptime(x, "%Y-%m-%d")), -) -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -def test_constructor_dict_datetime64_index(datetime_type): - # GH 9456 - dates = ["1984-02-19", "1988-11-06", "1989-12-03", "1990-03-15"] - values = [42544017.198965244, 1234565, 40512335.181958228, -1] - - result = SparseSeries(dict(zip(map(datetime_type, dates), values))) - expected = SparseSeries(values, map(pd.Timestamp, dates)) - - tm.assert_sp_series_equal(result, expected) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") -def test_to_sparse(): - # https://github.com/pandas-dev/pandas/issues/22389 - arr = pd.SparseArray([1, 2, None, 3]) - result = pd.Series(arr).to_sparse() - assert len(result) == 4 - tm.assert_sp_array_equal(result.values, arr, check_kind=False) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -def test_deprecated_to_sparse(): - # GH 26557 - # Deprecated 0.25.0 - - ser = Series([1, np.nan, 3]) - sparse_ser = pd.SparseSeries([1, np.nan, 3]) - - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): - result = ser.to_sparse() - tm.assert_series_equal(result, sparse_ser) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -def test_constructor_mismatched_raises(): - msg = "Length of passed values is 2, index implies 3" - with pytest.raises(ValueError, match=msg): - SparseSeries([1, 2], index=[1, 2, 3]) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -def test_block_deprecated(): - s = SparseSeries([1]) - with tm.assert_produces_warning(FutureWarning): - s.block diff --git a/pandas/tests/sparse/test_combine_concat.py b/pandas/tests/sparse/test_combine_concat.py index c553cd3fd1a7a..4ad1aa60e7b4f 100644 --- a/pandas/tests/sparse/test_combine_concat.py +++ b/pandas/tests/sparse/test_combine_concat.py @@ -1,10 +1,6 @@ -import itertools - import numpy as np import pytest -from pandas.errors import PerformanceWarning - import pandas as pd import pandas.util.testing as tm @@ -33,442 +29,3 @@ def test_uses_first_kind(self, kind): expected = np.array([1, 2, 1, 2, 2], dtype="int64") tm.assert_numpy_array_equal(result.sp_values, expected) assert result.kind == kind - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -class TestSparseSeriesConcat: - @pytest.mark.parametrize("kind", ["integer", "block"]) - def test_concat(self, kind): - val1 = np.array([1, 2, np.nan, np.nan, 0, np.nan]) - val2 = np.array([3, np.nan, 4, 0, 0]) - - sparse1 = pd.SparseSeries(val1, name="x", kind=kind) - sparse2 = pd.SparseSeries(val2, name="y", kind=kind) - - res = pd.concat([sparse1, sparse2]) - exp = pd.concat([pd.Series(val1), pd.Series(val2)]) - exp = pd.SparseSeries(exp, kind=kind) - tm.assert_sp_series_equal(res, exp, consolidate_block_indices=True) - - sparse1 = pd.SparseSeries(val1, fill_value=0, name="x", kind=kind) - sparse2 = pd.SparseSeries(val2, fill_value=0, name="y", kind=kind) - - res = pd.concat([sparse1, sparse2]) - exp = pd.concat([pd.Series(val1), pd.Series(val2)]) - exp = pd.SparseSeries(exp, fill_value=0, kind=kind) - tm.assert_sp_series_equal(res, exp, consolidate_block_indices=True) - - def test_concat_axis1(self): - val1 = np.array([1, 2, np.nan, np.nan, 0, np.nan]) - val2 = np.array([3, np.nan, 4, 0, 0]) - - sparse1 = pd.SparseSeries(val1, name="x") - sparse2 = pd.SparseSeries(val2, name="y") - - res = pd.concat([sparse1, sparse2], axis=1) - exp = pd.concat([pd.Series(val1, name="x"), pd.Series(val2, name="y")], axis=1) - exp = pd.SparseDataFrame(exp) - tm.assert_sp_frame_equal(res, exp, consolidate_block_indices=True) - - def test_concat_different_fill(self): - val1 = np.array([1, 2, np.nan, np.nan, 0, np.nan]) - val2 = np.array([3, np.nan, 4, 0, 0]) - - for kind in ["integer", "block"]: - sparse1 = pd.SparseSeries(val1, name="x", kind=kind) - sparse2 = pd.SparseSeries(val2, name="y", kind=kind, fill_value=0) - - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - res = pd.concat([sparse1, sparse2]) - - exp = pd.concat([pd.Series(val1), pd.Series(val2)]) - exp = pd.SparseSeries(exp, kind=kind) - tm.assert_sp_series_equal(res, exp) - - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - res = pd.concat([sparse2, sparse1]) - - exp = pd.concat([pd.Series(val2), pd.Series(val1)]) - exp = pd.SparseSeries(exp, kind=kind, fill_value=0) - tm.assert_sp_series_equal(res, exp) - - def test_concat_axis1_different_fill(self): - val1 = np.array([1, 2, np.nan, np.nan, 0, np.nan]) - val2 = np.array([3, np.nan, 4, 0, 0]) - - sparse1 = pd.SparseSeries(val1, name="x") - sparse2 = pd.SparseSeries(val2, name="y", fill_value=0) - - res = pd.concat([sparse1, sparse2], axis=1) - exp = pd.concat([pd.Series(val1, name="x"), pd.Series(val2, name="y")], axis=1) - assert isinstance(res, pd.SparseDataFrame) - tm.assert_frame_equal(res.to_dense(), exp) - - def test_concat_different_kind(self): - val1 = np.array([1, 2, np.nan, np.nan, 0, np.nan]) - val2 = np.array([3, np.nan, 4, 0, 0]) - - sparse1 = pd.SparseSeries(val1, name="x", kind="integer") - sparse2 = pd.SparseSeries(val2, name="y", kind="block") - - res = pd.concat([sparse1, sparse2]) - exp = pd.concat([pd.Series(val1), pd.Series(val2)]) - exp = pd.SparseSeries(exp, kind=sparse1.kind) - tm.assert_sp_series_equal(res, exp) - - res = pd.concat([sparse2, sparse1]) - exp = pd.concat([pd.Series(val2), pd.Series(val1)]) - exp = pd.SparseSeries(exp, kind=sparse2.kind) - tm.assert_sp_series_equal(res, exp, consolidate_block_indices=True) - - @pytest.mark.parametrize("kind", ["integer", "block"]) - def test_concat_sparse_dense(self, kind): - # use first input's fill_value - val1 = np.array([1, 2, np.nan, np.nan, 0, np.nan]) - val2 = np.array([3, np.nan, 4, 0, 0]) - - sparse = pd.SparseSeries(val1, name="x", kind=kind) - dense = pd.Series(val2, name="y") - - res = pd.concat([sparse, dense]) - exp = pd.SparseSeries(pd.concat([pd.Series(val1), dense]), kind=kind) - tm.assert_sp_series_equal(res, exp) - - res = pd.concat([dense, sparse, dense]) - exp = pd.concat([dense, pd.Series(val1), dense]) - # XXX: changed from SparseSeries to Series[sparse] - exp = pd.Series(pd.SparseArray(exp, kind=kind), index=exp.index, name=exp.name) - tm.assert_series_equal(res, exp) - - sparse = pd.SparseSeries(val1, name="x", kind=kind, fill_value=0) - dense = pd.Series(val2, name="y") - - res = pd.concat([sparse, dense]) - # XXX: changed from SparseSeries to Series[sparse] - exp = pd.concat([pd.Series(val1), dense]) - exp = pd.Series( - pd.SparseArray(exp, kind=kind, fill_value=0), index=exp.index, name=exp.name - ) - tm.assert_series_equal(res, exp) - - res = pd.concat([dense, sparse, dense]) - exp = pd.concat([dense, pd.Series(val1), dense]) - # XXX: changed from SparseSeries to Series[sparse] - exp = pd.Series( - pd.SparseArray(exp, kind=kind, fill_value=0), index=exp.index, name=exp.name - ) - tm.assert_series_equal(res, exp) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:DataFrame.to_sparse:FutureWarning") -class TestSparseDataFrameConcat: - def setup_method(self, method): - - self.dense1 = pd.DataFrame( - { - "A": [0.0, 1.0, 2.0, np.nan], - "B": [0.0, 0.0, 0.0, 0.0], - "C": [np.nan, np.nan, np.nan, np.nan], - "D": [1.0, 2.0, 3.0, 4.0], - } - ) - - self.dense2 = pd.DataFrame( - { - "A": [5.0, 6.0, 7.0, 8.0], - "B": [np.nan, 0.0, 7.0, 8.0], - "C": [5.0, 6.0, np.nan, np.nan], - "D": [np.nan, np.nan, np.nan, np.nan], - } - ) - - self.dense3 = pd.DataFrame( - { - "E": [5.0, 6.0, 7.0, 8.0], - "F": [np.nan, 0.0, 7.0, 8.0], - "G": [5.0, 6.0, np.nan, np.nan], - "H": [np.nan, np.nan, np.nan, np.nan], - } - ) - - def test_concat(self): - # fill_value = np.nan - sparse = self.dense1.to_sparse() - sparse2 = self.dense2.to_sparse() - - res = pd.concat([sparse, sparse]) - exp = pd.concat([self.dense1, self.dense1]).to_sparse() - tm.assert_sp_frame_equal(res, exp, consolidate_block_indices=True) - - res = pd.concat([sparse2, sparse2]) - exp = pd.concat([self.dense2, self.dense2]).to_sparse() - tm.assert_sp_frame_equal(res, exp, consolidate_block_indices=True) - - res = pd.concat([sparse, sparse2]) - exp = pd.concat([self.dense1, self.dense2]).to_sparse() - tm.assert_sp_frame_equal(res, exp, consolidate_block_indices=True) - - res = pd.concat([sparse2, sparse]) - exp = pd.concat([self.dense2, self.dense1]).to_sparse() - tm.assert_sp_frame_equal(res, exp, consolidate_block_indices=True) - - # fill_value = 0 - sparse = self.dense1.to_sparse(fill_value=0) - sparse2 = self.dense2.to_sparse(fill_value=0) - - res = pd.concat([sparse, sparse]) - exp = pd.concat([self.dense1, self.dense1]).to_sparse(fill_value=0) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(res, exp, consolidate_block_indices=True) - - res = pd.concat([sparse2, sparse2]) - exp = pd.concat([self.dense2, self.dense2]).to_sparse(fill_value=0) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(res, exp, consolidate_block_indices=True) - - res = pd.concat([sparse, sparse2]) - exp = pd.concat([self.dense1, self.dense2]).to_sparse(fill_value=0) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(res, exp, consolidate_block_indices=True) - - res = pd.concat([sparse2, sparse]) - exp = pd.concat([self.dense2, self.dense1]).to_sparse(fill_value=0) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(res, exp, consolidate_block_indices=True) - - def test_concat_different_fill_value(self): - # 1st fill_value will be used - sparse = self.dense1.to_sparse() - sparse2 = self.dense2.to_sparse(fill_value=0) - - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - res = pd.concat([sparse, sparse2]) - exp = pd.concat([self.dense1, self.dense2]).to_sparse() - tm.assert_sp_frame_equal(res, exp, consolidate_block_indices=True) - - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - res = pd.concat([sparse2, sparse]) - exp = pd.concat([self.dense2, self.dense1]).to_sparse(fill_value=0) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(res, exp, consolidate_block_indices=True) - - def test_concat_different_columns_sort_warns(self): - sparse = self.dense1.to_sparse() - sparse3 = self.dense3.to_sparse() - - # stacklevel is wrong since we have two FutureWarnings, - # one for depr, one for sorting. - with tm.assert_produces_warning( - FutureWarning, check_stacklevel=False, raise_on_extra_warnings=False - ): - res = pd.concat([sparse, sparse3]) - with tm.assert_produces_warning( - FutureWarning, check_stacklevel=False, raise_on_extra_warnings=False - ): - exp = pd.concat([self.dense1, self.dense3]) - - exp = exp.to_sparse() - tm.assert_sp_frame_equal(res, exp, check_kind=False) - - def test_concat_different_columns(self): - # fill_value = np.nan - sparse = self.dense1.to_sparse() - sparse3 = self.dense3.to_sparse() - - res = pd.concat([sparse, sparse3], sort=True) - exp = pd.concat([self.dense1, self.dense3], sort=True).to_sparse() - tm.assert_sp_frame_equal(res, exp, check_kind=False) - - res = pd.concat([sparse3, sparse], sort=True) - exp = pd.concat([self.dense3, self.dense1], sort=True).to_sparse() - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(res, exp, check_kind=False) - - def test_concat_bug(self): - from pandas.core.sparse.api import SparseDtype - - x = pd.SparseDataFrame({"A": pd.SparseArray([np.nan, np.nan], fill_value=0)}) - y = pd.SparseDataFrame({"B": []}) - res = pd.concat([x, y], sort=False)[["A"]] - exp = pd.DataFrame( - {"A": pd.SparseArray([np.nan, np.nan], dtype=SparseDtype(float, 0))} - ) - tm.assert_frame_equal(res, exp) - - def test_concat_different_columns_buggy(self): - sparse = self.dense1.to_sparse(fill_value=0) - sparse3 = self.dense3.to_sparse(fill_value=0) - - res = pd.concat([sparse, sparse3], sort=True) - exp = pd.concat([self.dense1, self.dense3], sort=True).to_sparse(fill_value=0) - exp._default_fill_value = np.nan - - tm.assert_sp_frame_equal( - res, exp, check_kind=False, consolidate_block_indices=True - ) - - res = pd.concat([sparse3, sparse], sort=True) - exp = pd.concat([self.dense3, self.dense1], sort=True).to_sparse(fill_value=0) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal( - res, exp, check_kind=False, consolidate_block_indices=True - ) - - # different fill values - sparse = self.dense1.to_sparse() - sparse3 = self.dense3.to_sparse(fill_value=0) - # each columns keeps its fill_value, thus compare in dense - res = pd.concat([sparse, sparse3], sort=True) - exp = pd.concat([self.dense1, self.dense3], sort=True) - assert isinstance(res, pd.SparseDataFrame) - tm.assert_frame_equal(res.to_dense(), exp) - - res = pd.concat([sparse3, sparse], sort=True) - exp = pd.concat([self.dense3, self.dense1], sort=True) - assert isinstance(res, pd.SparseDataFrame) - tm.assert_frame_equal(res.to_dense(), exp) - - def test_concat_series(self): - # fill_value = np.nan - sparse = self.dense1.to_sparse() - sparse2 = self.dense2.to_sparse() - - for col in ["A", "D"]: - res = pd.concat([sparse, sparse2[col]]) - exp = pd.concat([self.dense1, self.dense2[col]]).to_sparse() - tm.assert_sp_frame_equal(res, exp, check_kind=False) - - res = pd.concat([sparse2[col], sparse]) - exp = pd.concat([self.dense2[col], self.dense1]).to_sparse() - tm.assert_sp_frame_equal(res, exp, check_kind=False) - - # fill_value = 0 - sparse = self.dense1.to_sparse(fill_value=0) - sparse2 = self.dense2.to_sparse(fill_value=0) - - for col in ["C", "D"]: - res = pd.concat([sparse, sparse2[col]]) - exp = pd.concat([self.dense1, self.dense2[col]]).to_sparse(fill_value=0) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal( - res, exp, check_kind=False, consolidate_block_indices=True - ) - - res = pd.concat([sparse2[col], sparse]) - exp = pd.concat([self.dense2[col], self.dense1]).to_sparse(fill_value=0) - exp["C"] = res["C"] - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal( - res, exp, consolidate_block_indices=True, check_kind=False - ) - - def test_concat_axis1(self): - # fill_value = np.nan - sparse = self.dense1.to_sparse() - sparse3 = self.dense3.to_sparse() - - res = pd.concat([sparse, sparse3], axis=1) - exp = pd.concat([self.dense1, self.dense3], axis=1).to_sparse() - tm.assert_sp_frame_equal(res, exp) - - res = pd.concat([sparse3, sparse], axis=1) - exp = pd.concat([self.dense3, self.dense1], axis=1).to_sparse() - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(res, exp) - - # fill_value = 0 - sparse = self.dense1.to_sparse(fill_value=0) - sparse3 = self.dense3.to_sparse(fill_value=0) - - res = pd.concat([sparse, sparse3], axis=1) - exp = pd.concat([self.dense1, self.dense3], axis=1).to_sparse(fill_value=0) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(res, exp) - - res = pd.concat([sparse3, sparse], axis=1) - exp = pd.concat([self.dense3, self.dense1], axis=1).to_sparse(fill_value=0) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(res, exp) - - # different fill values - sparse = self.dense1.to_sparse() - sparse3 = self.dense3.to_sparse(fill_value=0) - # each columns keeps its fill_value, thus compare in dense - res = pd.concat([sparse, sparse3], axis=1) - exp = pd.concat([self.dense1, self.dense3], axis=1) - assert isinstance(res, pd.SparseDataFrame) - tm.assert_frame_equal(res.to_dense(), exp) - - res = pd.concat([sparse3, sparse], axis=1) - exp = pd.concat([self.dense3, self.dense1], axis=1) - assert isinstance(res, pd.SparseDataFrame) - tm.assert_frame_equal(res.to_dense(), exp) - - @pytest.mark.parametrize( - "fill_value,sparse_idx,dense_idx", - itertools.product([None, 0, 1, np.nan], [0, 1], [1, 0]), - ) - def test_concat_sparse_dense_rows(self, fill_value, sparse_idx, dense_idx): - frames = [self.dense1, self.dense2] - sparse_frame = [ - frames[dense_idx], - frames[sparse_idx].to_sparse(fill_value=fill_value), - ] - dense_frame = [frames[dense_idx], frames[sparse_idx]] - - # This will try both directions sparse + dense and dense + sparse - for _ in range(2): - res = pd.concat(sparse_frame) - exp = pd.concat(dense_frame) - - assert isinstance(res, pd.SparseDataFrame) - tm.assert_frame_equal(res.to_dense(), exp) - - sparse_frame = sparse_frame[::-1] - dense_frame = dense_frame[::-1] - - @pytest.mark.parametrize( - "fill_value,sparse_idx,dense_idx", - itertools.product([None, 0, 1, np.nan], [0, 1], [1, 0]), - ) - @pytest.mark.xfail(reason="The iloc fails and I can't make expected", strict=True) - def test_concat_sparse_dense_cols(self, fill_value, sparse_idx, dense_idx): - # See GH16874, GH18914 and #18686 for why this should be a DataFrame - from pandas.core.dtypes.common import is_sparse - - frames = [self.dense1, self.dense3] - - sparse_frame = [ - frames[dense_idx], - frames[sparse_idx].to_sparse(fill_value=fill_value), - ] - dense_frame = [frames[dense_idx], frames[sparse_idx]] - - # This will try both directions sparse + dense and dense + sparse - for _ in range(2): - res = pd.concat(sparse_frame, axis=1) - exp = pd.concat(dense_frame, axis=1) - cols = [i for (i, x) in enumerate(res.dtypes) if is_sparse(x)] - - for col in cols: - exp.iloc[:, col] = exp.iloc[:, col].astype("Sparse") - - for column in frames[dense_idx].columns: - if dense_idx == sparse_idx: - tm.assert_frame_equal(res[column], exp[column]) - else: - tm.assert_series_equal(res[column], exp[column]) - - tm.assert_frame_equal(res, exp) - - sparse_frame = sparse_frame[::-1] - dense_frame = dense_frame[::-1] diff --git a/pandas/tests/sparse/test_format.py b/pandas/tests/sparse/test_format.py index cf8734910cd19..e69de29bb2d1d 100644 --- a/pandas/tests/sparse/test_format.py +++ b/pandas/tests/sparse/test_format.py @@ -1,165 +0,0 @@ -import warnings - -import numpy as np -import pytest - -from pandas.compat import is_platform_32bit, is_platform_windows - -import pandas as pd -from pandas import option_context -import pandas.util.testing as tm - -use_32bit_repr = is_platform_windows() or is_platform_32bit() - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") -class TestSparseSeriesFormatting: - @property - def dtype_format_for_platform(self): - return "" if use_32bit_repr else ", dtype=int32" - - def test_sparse_max_row(self): - s = pd.Series([1, np.nan, np.nan, 3, np.nan]).to_sparse() - result = repr(s) - dfm = self.dtype_format_for_platform - exp = ( - "0 1.0\n1 NaN\n2 NaN\n3 3.0\n" - "4 NaN\ndtype: Sparse[float64, nan]\nBlockIndex\n" - "Block locations: array([0, 3]{0})\n" - "Block lengths: array([1, 1]{0})".format(dfm) - ) - assert result == exp - - def test_sparsea_max_row_truncated(self): - s = pd.Series([1, np.nan, np.nan, 3, np.nan]).to_sparse() - dfm = self.dtype_format_for_platform - - with option_context("display.max_rows", 3): - # GH 10560 - result = repr(s) - exp = ( - "0 1.0\n ... \n4 NaN\n" - "Length: 5, dtype: Sparse[float64, nan]\nBlockIndex\n" - "Block locations: array([0, 3]{0})\n" - "Block lengths: array([1, 1]{0})".format(dfm) - ) - assert result == exp - - def test_sparse_mi_max_row(self): - idx = pd.MultiIndex.from_tuples( - [("A", 0), ("A", 1), ("B", 0), ("C", 0), ("C", 1), ("C", 2)] - ) - s = pd.Series([1, np.nan, np.nan, 3, np.nan, np.nan], index=idx).to_sparse() - result = repr(s) - dfm = self.dtype_format_for_platform - exp = ( - "A 0 1.0\n 1 NaN\nB 0 NaN\n" - "C 0 3.0\n 1 NaN\n 2 NaN\n" - "dtype: Sparse[float64, nan]\nBlockIndex\n" - "Block locations: array([0, 3]{0})\n" - "Block lengths: array([1, 1]{0})".format(dfm) - ) - assert result == exp - - with option_context("display.max_rows", 3, "display.show_dimensions", False): - # GH 13144 - result = repr(s) - exp = ( - "A 0 1.0\n ... \nC 2 NaN\n" - "dtype: Sparse[float64, nan]\nBlockIndex\n" - "Block locations: array([0, 3]{0})\n" - "Block lengths: array([1, 1]{0})".format(dfm) - ) - assert result == exp - - def test_sparse_bool(self): - # GH 13110 - s = pd.SparseSeries([True, False, False, True, False, False], fill_value=False) - result = repr(s) - dtype = "" if use_32bit_repr else ", dtype=int32" - exp = ( - "0 True\n1 False\n2 False\n" - "3 True\n4 False\n5 False\n" - "dtype: Sparse[bool, False]\nBlockIndex\n" - "Block locations: array([0, 3]{0})\n" - "Block lengths: array([1, 1]{0})".format(dtype) - ) - assert result == exp - - with option_context("display.max_rows", 3): - result = repr(s) - exp = ( - "0 True\n ... \n5 False\n" - "Length: 6, dtype: Sparse[bool, False]\nBlockIndex\n" - "Block locations: array([0, 3]{0})\n" - "Block lengths: array([1, 1]{0})".format(dtype) - ) - assert result == exp - - def test_sparse_int(self): - # GH 13110 - s = pd.SparseSeries([0, 1, 0, 0, 1, 0], fill_value=False) - - result = repr(s) - dtype = "" if use_32bit_repr else ", dtype=int32" - exp = ( - "0 0\n1 1\n2 0\n3 0\n4 1\n" - "5 0\ndtype: Sparse[int64, False]\nBlockIndex\n" - "Block locations: array([1, 4]{0})\n" - "Block lengths: array([1, 1]{0})".format(dtype) - ) - assert result == exp - - with option_context("display.max_rows", 3, "display.show_dimensions", False): - result = repr(s) - exp = ( - "0 0\n ..\n5 0\n" - "dtype: Sparse[int64, False]\nBlockIndex\n" - "Block locations: array([1, 4]{0})\n" - "Block lengths: array([1, 1]{0})".format(dtype) - ) - assert result == exp - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:DataFrame.to_sparse:FutureWarning") -class TestSparseDataFrameFormatting: - def test_sparse_frame(self): - # GH 13110 - df = pd.DataFrame( - { - "A": [True, False, True, False, True], - "B": [True, False, True, False, True], - "C": [0, 0, 3, 0, 5], - "D": [np.nan, np.nan, np.nan, 1, 2], - } - ) - sparse = df.to_sparse() - assert repr(sparse) == repr(df) - - with option_context("display.max_rows", 3): - assert repr(sparse) == repr(df) - - def test_sparse_repr_after_set(self): - # GH 15488 - sdf = pd.SparseDataFrame([[np.nan, 1], [2, np.nan]]) - res = sdf.copy() - - # Ignore the warning - with pd.option_context("mode.chained_assignment", None): - sdf[0][1] = 2 # This line triggers the bug - - repr(sdf) - tm.assert_sp_frame_equal(sdf, res) - - -def test_repr_no_warning(): - with warnings.catch_warnings(): - warnings.simplefilter("ignore", FutureWarning) - df = pd.SparseDataFrame({"A": [1, 2]}) - s = df["A"] - - with tm.assert_produces_warning(None): - repr(df) - repr(s) diff --git a/pandas/tests/sparse/test_groupby.py b/pandas/tests/sparse/test_groupby.py index 04e49a272a77a..e69de29bb2d1d 100644 --- a/pandas/tests/sparse/test_groupby.py +++ b/pandas/tests/sparse/test_groupby.py @@ -1,73 +0,0 @@ -import numpy as np -import pytest - -import pandas as pd -import pandas.util.testing as tm - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:DataFrame.to_sparse:FutureWarning") -class TestSparseGroupBy: - def setup_method(self, method): - self.dense = pd.DataFrame( - { - "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"], - "B": ["one", "one", "two", "three", "two", "two", "one", "three"], - "C": np.random.randn(8), - "D": np.random.randn(8), - "E": [np.nan, np.nan, 1, 2, np.nan, 1, np.nan, np.nan], - } - ) - self.sparse = self.dense.to_sparse() - - def test_first_last_nth(self): - # tests for first / last / nth - sparse_grouped = self.sparse.groupby("A") - dense_grouped = self.dense.groupby("A") - - sparse_grouped_first = sparse_grouped.first() - sparse_grouped_last = sparse_grouped.last() - sparse_grouped_nth = sparse_grouped.nth(1) - - dense_grouped_first = pd.DataFrame(dense_grouped.first().to_sparse()) - dense_grouped_last = pd.DataFrame(dense_grouped.last().to_sparse()) - dense_grouped_nth = pd.DataFrame(dense_grouped.nth(1).to_sparse()) - - tm.assert_frame_equal(sparse_grouped_first, dense_grouped_first) - tm.assert_frame_equal(sparse_grouped_last, dense_grouped_last) - tm.assert_frame_equal(sparse_grouped_nth, dense_grouped_nth) - - def test_aggfuncs(self): - sparse_grouped = self.sparse.groupby("A") - dense_grouped = self.dense.groupby("A") - - result = sparse_grouped.mean().to_sparse() - expected = dense_grouped.mean().to_sparse() - - tm.assert_frame_equal(result, expected) - - # ToDo: sparse sum includes str column - # tm.assert_frame_equal(sparse_grouped.sum(), - # dense_grouped.sum()) - - result = sparse_grouped.count().to_sparse() - expected = dense_grouped.count().to_sparse() - - tm.assert_frame_equal(result, expected) - - -@pytest.mark.parametrize("fill_value", [0, np.nan]) -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:DataFrame.to_sparse:FutureWarning") -def test_groupby_includes_fill_value(fill_value): - # https://github.com/pandas-dev/pandas/issues/5078 - df = pd.DataFrame( - { - "a": [fill_value, 1, fill_value, fill_value], - "b": [fill_value, 1, fill_value, fill_value], - } - ) - sdf = df.to_sparse(fill_value=fill_value) - result = sdf.groupby("a").sum() - expected = pd.DataFrame(df.groupby("a").sum().to_sparse(fill_value=fill_value)) - tm.assert_frame_equal(result, expected, check_index_type=False) diff --git a/pandas/tests/sparse/test_indexing.py b/pandas/tests/sparse/test_indexing.py index ea5e939b57566..e69de29bb2d1d 100644 --- a/pandas/tests/sparse/test_indexing.py +++ b/pandas/tests/sparse/test_indexing.py @@ -1,1058 +0,0 @@ -import numpy as np -import pytest - -import pandas as pd -from pandas.core.sparse.api import SparseDtype -import pandas.util.testing as tm - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") -class TestSparseSeriesIndexing: - def setup_method(self, method): - self.orig = pd.Series([1, np.nan, np.nan, 3, np.nan]) - self.sparse = self.orig.to_sparse() - - def test_getitem(self): - orig = self.orig - sparse = self.sparse - - assert sparse[0] == 1 - assert np.isnan(sparse[1]) - assert sparse[3] == 3 - - result = sparse[[1, 3, 4]] - exp = orig[[1, 3, 4]].to_sparse() - tm.assert_sp_series_equal(result, exp) - - # dense array - result = sparse[orig % 2 == 1] - exp = orig[orig % 2 == 1].to_sparse() - tm.assert_sp_series_equal(result, exp) - - # sparse array (actuary it coerces to normal Series) - result = sparse[sparse % 2 == 1] - exp = orig[orig % 2 == 1].to_sparse() - tm.assert_sp_series_equal(result, exp) - - # sparse array - result = sparse[pd.SparseArray(sparse % 2 == 1, dtype=bool)] - tm.assert_sp_series_equal(result, exp) - - def test_getitem_slice(self): - orig = self.orig - sparse = self.sparse - - tm.assert_sp_series_equal(sparse[:2], orig[:2].to_sparse()) - tm.assert_sp_series_equal(sparse[4:2], orig[4:2].to_sparse()) - tm.assert_sp_series_equal(sparse[::2], orig[::2].to_sparse()) - tm.assert_sp_series_equal(sparse[-5:], orig[-5:].to_sparse()) - - def test_getitem_int_dtype(self): - # GH 8292 - s = pd.SparseSeries([0, 1, 2, 3, 4, 5, 6], name="xxx") - res = s[::2] - exp = pd.SparseSeries([0, 2, 4, 6], index=[0, 2, 4, 6], name="xxx") - tm.assert_sp_series_equal(res, exp) - assert res.dtype == SparseDtype(np.int64) - - s = pd.SparseSeries([0, 1, 2, 3, 4, 5, 6], fill_value=0, name="xxx") - res = s[::2] - exp = pd.SparseSeries( - [0, 2, 4, 6], index=[0, 2, 4, 6], fill_value=0, name="xxx" - ) - tm.assert_sp_series_equal(res, exp) - assert res.dtype == SparseDtype(np.int64) - - def test_getitem_fill_value(self): - orig = pd.Series([1, np.nan, 0, 3, 0]) - sparse = orig.to_sparse(fill_value=0) - - assert sparse[0] == 1 - assert np.isnan(sparse[1]) - assert sparse[2] == 0 - assert sparse[3] == 3 - - result = sparse[[1, 3, 4]] - exp = orig[[1, 3, 4]].to_sparse(fill_value=0) - tm.assert_sp_series_equal(result, exp) - - # dense array - result = sparse[orig % 2 == 1] - exp = orig[orig % 2 == 1].to_sparse(fill_value=0) - tm.assert_sp_series_equal(result, exp) - - # sparse array (actuary it coerces to normal Series) - result = sparse[sparse % 2 == 1] - exp = orig[orig % 2 == 1].to_sparse(fill_value=0) - tm.assert_sp_series_equal(result, exp) - - # sparse array - result = sparse[pd.SparseArray(sparse % 2 == 1, dtype=bool)] - tm.assert_sp_series_equal(result, exp) - - def test_getitem_ellipsis(self): - # GH 9467 - s = pd.SparseSeries([1, np.nan, 2, 0, np.nan]) - tm.assert_sp_series_equal(s[...], s) - - s = pd.SparseSeries([1, np.nan, 2, 0, np.nan], fill_value=0) - tm.assert_sp_series_equal(s[...], s) - - def test_getitem_slice_fill_value(self): - orig = pd.Series([1, np.nan, 0, 3, 0]) - sparse = orig.to_sparse(fill_value=0) - tm.assert_sp_series_equal(sparse[:2], orig[:2].to_sparse(fill_value=0)) - tm.assert_sp_series_equal(sparse[4:2], orig[4:2].to_sparse(fill_value=0)) - tm.assert_sp_series_equal(sparse[::2], orig[::2].to_sparse(fill_value=0)) - tm.assert_sp_series_equal(sparse[-5:], orig[-5:].to_sparse(fill_value=0)) - - def test_loc(self): - orig = self.orig - sparse = self.sparse - - assert sparse.loc[0] == 1 - assert np.isnan(sparse.loc[1]) - - result = sparse.loc[[1, 3, 4]] - exp = orig.loc[[1, 3, 4]].to_sparse() - tm.assert_sp_series_equal(result, exp) - - # exceeds the bounds - result = sparse.reindex([1, 3, 4, 5]) - exp = orig.reindex([1, 3, 4, 5]).to_sparse() - tm.assert_sp_series_equal(result, exp) - # padded with NaN - assert np.isnan(result[-1]) - - # dense array - result = sparse.loc[orig % 2 == 1] - exp = orig.loc[orig % 2 == 1].to_sparse() - tm.assert_sp_series_equal(result, exp) - - # sparse array (actuary it coerces to normal Series) - result = sparse.loc[sparse % 2 == 1] - exp = orig.loc[orig % 2 == 1].to_sparse() - tm.assert_sp_series_equal(result, exp) - - # sparse array - result = sparse.loc[pd.SparseArray(sparse % 2 == 1, dtype=bool)] - tm.assert_sp_series_equal(result, exp) - - def test_loc_index(self): - orig = pd.Series([1, np.nan, np.nan, 3, np.nan], index=list("ABCDE")) - sparse = orig.to_sparse() - - assert sparse.loc["A"] == 1 - assert np.isnan(sparse.loc["B"]) - - result = sparse.loc[["A", "C", "D"]] - exp = orig.loc[["A", "C", "D"]].to_sparse() - tm.assert_sp_series_equal(result, exp) - - # dense array - result = sparse.loc[orig % 2 == 1] - exp = orig.loc[orig % 2 == 1].to_sparse() - tm.assert_sp_series_equal(result, exp) - - # sparse array (actuary it coerces to normal Series) - result = sparse.loc[sparse % 2 == 1] - exp = orig.loc[orig % 2 == 1].to_sparse() - tm.assert_sp_series_equal(result, exp) - - # sparse array - result = sparse[pd.SparseArray(sparse % 2 == 1, dtype=bool)] - tm.assert_sp_series_equal(result, exp) - - def test_loc_index_fill_value(self): - orig = pd.Series([1, np.nan, 0, 3, 0], index=list("ABCDE")) - sparse = orig.to_sparse(fill_value=0) - - assert sparse.loc["A"] == 1 - assert np.isnan(sparse.loc["B"]) - - result = sparse.loc[["A", "C", "D"]] - exp = orig.loc[["A", "C", "D"]].to_sparse(fill_value=0) - tm.assert_sp_series_equal(result, exp) - - # dense array - result = sparse.loc[orig % 2 == 1] - exp = orig.loc[orig % 2 == 1].to_sparse(fill_value=0) - tm.assert_sp_series_equal(result, exp) - - # sparse array (actuary it coerces to normal Series) - result = sparse.loc[sparse % 2 == 1] - exp = orig.loc[orig % 2 == 1].to_sparse(fill_value=0) - tm.assert_sp_series_equal(result, exp) - - def test_loc_slice(self): - orig = self.orig - sparse = self.sparse - tm.assert_sp_series_equal(sparse.loc[2:], orig.loc[2:].to_sparse()) - - def test_loc_slice_index_fill_value(self): - orig = pd.Series([1, np.nan, 0, 3, 0], index=list("ABCDE")) - sparse = orig.to_sparse(fill_value=0) - - tm.assert_sp_series_equal( - sparse.loc["C":], orig.loc["C":].to_sparse(fill_value=0) - ) - - def test_loc_slice_fill_value(self): - orig = pd.Series([1, np.nan, 0, 3, 0]) - sparse = orig.to_sparse(fill_value=0) - tm.assert_sp_series_equal(sparse.loc[2:], orig.loc[2:].to_sparse(fill_value=0)) - - def test_iloc(self): - orig = self.orig - sparse = self.sparse - - assert sparse.iloc[3] == 3 - assert np.isnan(sparse.iloc[2]) - - result = sparse.iloc[[1, 3, 4]] - exp = orig.iloc[[1, 3, 4]].to_sparse() - tm.assert_sp_series_equal(result, exp) - - result = sparse.iloc[[1, -2, -4]] - exp = orig.iloc[[1, -2, -4]].to_sparse() - tm.assert_sp_series_equal(result, exp) - - with pytest.raises(IndexError): - sparse.iloc[[1, 3, 5]] - - def test_iloc_fill_value(self): - orig = pd.Series([1, np.nan, 0, 3, 0]) - sparse = orig.to_sparse(fill_value=0) - - assert sparse.iloc[3] == 3 - assert np.isnan(sparse.iloc[1]) - assert sparse.iloc[4] == 0 - - result = sparse.iloc[[1, 3, 4]] - exp = orig.iloc[[1, 3, 4]].to_sparse(fill_value=0) - tm.assert_sp_series_equal(result, exp) - - def test_iloc_slice(self): - orig = pd.Series([1, np.nan, np.nan, 3, np.nan]) - sparse = orig.to_sparse() - tm.assert_sp_series_equal(sparse.iloc[2:], orig.iloc[2:].to_sparse()) - - def test_iloc_slice_fill_value(self): - orig = pd.Series([1, np.nan, 0, 3, 0]) - sparse = orig.to_sparse(fill_value=0) - tm.assert_sp_series_equal( - sparse.iloc[2:], orig.iloc[2:].to_sparse(fill_value=0) - ) - - def test_at(self): - orig = pd.Series([1, np.nan, np.nan, 3, np.nan]) - sparse = orig.to_sparse() - assert sparse.at[0] == orig.at[0] - assert np.isnan(sparse.at[1]) - assert np.isnan(sparse.at[2]) - assert sparse.at[3] == orig.at[3] - assert np.isnan(sparse.at[4]) - - orig = pd.Series([1, np.nan, np.nan, 3, np.nan], index=list("abcde")) - sparse = orig.to_sparse() - assert sparse.at["a"] == orig.at["a"] - assert np.isnan(sparse.at["b"]) - assert np.isnan(sparse.at["c"]) - assert sparse.at["d"] == orig.at["d"] - assert np.isnan(sparse.at["e"]) - - def test_at_fill_value(self): - orig = pd.Series([1, np.nan, 0, 3, 0], index=list("abcde")) - sparse = orig.to_sparse(fill_value=0) - assert sparse.at["a"] == orig.at["a"] - assert np.isnan(sparse.at["b"]) - assert sparse.at["c"] == orig.at["c"] - assert sparse.at["d"] == orig.at["d"] - assert sparse.at["e"] == orig.at["e"] - - def test_iat(self): - orig = self.orig - sparse = self.sparse - - assert sparse.iat[0] == orig.iat[0] - assert np.isnan(sparse.iat[1]) - assert np.isnan(sparse.iat[2]) - assert sparse.iat[3] == orig.iat[3] - assert np.isnan(sparse.iat[4]) - - assert np.isnan(sparse.iat[-1]) - assert sparse.iat[-5] == orig.iat[-5] - - def test_iat_fill_value(self): - orig = pd.Series([1, np.nan, 0, 3, 0]) - sparse = orig.to_sparse() - assert sparse.iat[0] == orig.iat[0] - assert np.isnan(sparse.iat[1]) - assert sparse.iat[2] == orig.iat[2] - assert sparse.iat[3] == orig.iat[3] - assert sparse.iat[4] == orig.iat[4] - - assert sparse.iat[-1] == orig.iat[-1] - assert sparse.iat[-5] == orig.iat[-5] - - def test_get(self): - s = pd.SparseSeries([1, np.nan, np.nan, 3, np.nan]) - assert s.get(0) == 1 - assert np.isnan(s.get(1)) - assert s.get(5) is None - - s = pd.SparseSeries([1, np.nan, 0, 3, 0], index=list("ABCDE")) - assert s.get("A") == 1 - assert np.isnan(s.get("B")) - assert s.get("C") == 0 - assert s.get("XX") is None - - s = pd.SparseSeries([1, np.nan, 0, 3, 0], index=list("ABCDE"), fill_value=0) - assert s.get("A") == 1 - assert np.isnan(s.get("B")) - assert s.get("C") == 0 - assert s.get("XX") is None - - def test_take(self): - orig = pd.Series([1, np.nan, np.nan, 3, np.nan], index=list("ABCDE")) - sparse = orig.to_sparse() - - tm.assert_sp_series_equal(sparse.take([0]), orig.take([0]).to_sparse()) - tm.assert_sp_series_equal( - sparse.take([0, 1, 3]), orig.take([0, 1, 3]).to_sparse() - ) - tm.assert_sp_series_equal( - sparse.take([-1, -2]), orig.take([-1, -2]).to_sparse() - ) - - def test_take_fill_value(self): - orig = pd.Series([1, np.nan, 0, 3, 0], index=list("ABCDE")) - sparse = orig.to_sparse(fill_value=0) - - tm.assert_sp_series_equal( - sparse.take([0]), orig.take([0]).to_sparse(fill_value=0) - ) - - exp = orig.take([0, 1, 3]).to_sparse(fill_value=0) - tm.assert_sp_series_equal(sparse.take([0, 1, 3]), exp) - - exp = orig.take([-1, -2]).to_sparse(fill_value=0) - tm.assert_sp_series_equal(sparse.take([-1, -2]), exp) - - def test_reindex(self): - orig = pd.Series([1, np.nan, np.nan, 3, np.nan], index=list("ABCDE")) - sparse = orig.to_sparse() - - res = sparse.reindex(["A", "E", "C", "D"]) - exp = orig.reindex(["A", "E", "C", "D"]).to_sparse() - tm.assert_sp_series_equal(res, exp) - - # all missing & fill_value - res = sparse.reindex(["B", "E", "C"]) - exp = orig.reindex(["B", "E", "C"]).to_sparse() - tm.assert_sp_series_equal(res, exp) - - orig = pd.Series([np.nan, np.nan, np.nan, np.nan, np.nan], index=list("ABCDE")) - sparse = orig.to_sparse() - - res = sparse.reindex(["A", "E", "C", "D"]) - exp = orig.reindex(["A", "E", "C", "D"]).to_sparse() - tm.assert_sp_series_equal(res, exp) - - def test_fill_value_reindex(self): - orig = pd.Series([1, np.nan, 0, 3, 0], index=list("ABCDE")) - sparse = orig.to_sparse(fill_value=0) - - res = sparse.reindex(["A", "E", "C", "D"]) - exp = orig.reindex(["A", "E", "C", "D"]).to_sparse(fill_value=0) - tm.assert_sp_series_equal(res, exp) - - # includes missing and fill_value - res = sparse.reindex(["A", "B", "C"]) - exp = orig.reindex(["A", "B", "C"]).to_sparse(fill_value=0) - tm.assert_sp_series_equal(res, exp) - - # all missing - orig = pd.Series([np.nan, np.nan, np.nan, np.nan, np.nan], index=list("ABCDE")) - sparse = orig.to_sparse(fill_value=0) - - res = sparse.reindex(["A", "E", "C", "D"]) - exp = orig.reindex(["A", "E", "C", "D"]).to_sparse(fill_value=0) - tm.assert_sp_series_equal(res, exp) - - # all fill_value - orig = pd.Series([0.0, 0.0, 0.0, 0.0, 0.0], index=list("ABCDE")) - sparse = orig.to_sparse(fill_value=0) - - def test_fill_value_reindex_coerces_float_int(self): - orig = pd.Series([1, np.nan, 0, 3, 0], index=list("ABCDE")) - sparse = orig.to_sparse(fill_value=0) - - res = sparse.reindex(["A", "E", "C", "D"]) - exp = orig.reindex(["A", "E", "C", "D"]).to_sparse(fill_value=0) - tm.assert_sp_series_equal(res, exp) - - def test_reindex_fill_value(self): - floats = pd.Series([1.0, 2.0, 3.0]).to_sparse() - result = floats.reindex([1, 2, 3], fill_value=0) - expected = pd.Series([2.0, 3.0, 0], index=[1, 2, 3]).to_sparse() - tm.assert_sp_series_equal(result, expected) - - def test_reindex_nearest(self): - s = pd.Series(np.arange(10, dtype="float64")).to_sparse() - target = [0.1, 0.9, 1.5, 2.0] - actual = s.reindex(target, method="nearest") - expected = pd.Series(np.around(target), target).to_sparse() - tm.assert_sp_series_equal(expected, actual) - - actual = s.reindex(target, method="nearest", tolerance=0.2) - expected = pd.Series([0, 1, np.nan, 2], target).to_sparse() - tm.assert_sp_series_equal(expected, actual) - - actual = s.reindex(target, method="nearest", tolerance=[0.3, 0.01, 0.4, 3]) - expected = pd.Series([0, np.nan, np.nan, 2], target).to_sparse() - tm.assert_sp_series_equal(expected, actual) - - @pytest.mark.parametrize("kind", ["integer", "block"]) - @pytest.mark.parametrize("fill", [True, False, np.nan]) - def tests_indexing_with_sparse(self, kind, fill): - # see gh-13985 - arr = pd.SparseArray([1, 2, 3], kind=kind) - indexer = pd.SparseArray([True, False, True], fill_value=fill, dtype=bool) - - expected = arr[indexer] - result = pd.SparseArray([1, 3], kind=kind) - tm.assert_sp_array_equal(result, expected) - - s = pd.SparseSeries(arr, index=["a", "b", "c"], dtype=np.float64) - expected = pd.SparseSeries( - [1, 3], - index=["a", "c"], - kind=kind, - dtype=SparseDtype(np.float64, s.fill_value), - ) - - tm.assert_sp_series_equal(s[indexer], expected) - tm.assert_sp_series_equal(s.loc[indexer], expected) - tm.assert_sp_series_equal(s.iloc[indexer], expected) - - indexer = pd.SparseSeries(indexer, index=["a", "b", "c"]) - tm.assert_sp_series_equal(s[indexer], expected) - tm.assert_sp_series_equal(s.loc[indexer], expected) - - msg = "iLocation based boolean indexing cannot use an indexable as a mask" - with pytest.raises(ValueError, match=msg): - s.iloc[indexer] - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -class TestSparseSeriesMultiIndexing(TestSparseSeriesIndexing): - def setup_method(self, method): - # Mi with duplicated values - idx = pd.MultiIndex.from_tuples( - [("A", 0), ("A", 1), ("B", 0), ("C", 0), ("C", 1)] - ) - self.orig = pd.Series([1, np.nan, np.nan, 3, np.nan], index=idx) - self.sparse = self.orig.to_sparse() - - def test_getitem_multi(self): - orig = self.orig - sparse = self.sparse - - assert sparse[0] == orig[0] - assert np.isnan(sparse[1]) - assert sparse[3] == orig[3] - - tm.assert_sp_series_equal(sparse["A"], orig["A"].to_sparse()) - tm.assert_sp_series_equal(sparse["B"], orig["B"].to_sparse()) - - result = sparse[[1, 3, 4]] - exp = orig[[1, 3, 4]].to_sparse() - tm.assert_sp_series_equal(result, exp) - - # dense array - result = sparse[orig % 2 == 1] - exp = orig[orig % 2 == 1].to_sparse() - tm.assert_sp_series_equal(result, exp) - - # sparse array (actuary it coerces to normal Series) - result = sparse[sparse % 2 == 1] - exp = orig[orig % 2 == 1].to_sparse() - tm.assert_sp_series_equal(result, exp) - - # sparse array - result = sparse[pd.SparseArray(sparse % 2 == 1, dtype=bool)] - tm.assert_sp_series_equal(result, exp) - - def test_getitem_multi_tuple(self): - orig = self.orig - sparse = self.sparse - - assert sparse["C", 0] == orig["C", 0] - assert np.isnan(sparse["A", 1]) - assert np.isnan(sparse["B", 0]) - - def test_getitems_slice_multi(self): - orig = self.orig - sparse = self.sparse - - tm.assert_sp_series_equal(sparse[2:], orig[2:].to_sparse()) - tm.assert_sp_series_equal(sparse.loc["B":], orig.loc["B":].to_sparse()) - tm.assert_sp_series_equal(sparse.loc["C":], orig.loc["C":].to_sparse()) - - tm.assert_sp_series_equal(sparse.loc["A":"B"], orig.loc["A":"B"].to_sparse()) - tm.assert_sp_series_equal(sparse.loc[:"B"], orig.loc[:"B"].to_sparse()) - - def test_loc(self): - # need to be override to use different label - orig = self.orig - sparse = self.sparse - - tm.assert_sp_series_equal(sparse.loc["A"], orig.loc["A"].to_sparse()) - tm.assert_sp_series_equal(sparse.loc["B"], orig.loc["B"].to_sparse()) - - result = sparse.loc[[1, 3, 4]] - exp = orig.loc[[1, 3, 4]].to_sparse() - tm.assert_sp_series_equal(result, exp) - - # exceeds the bounds - result = sparse.loc[[1, 3, 4, 5]] - exp = orig.loc[[1, 3, 4, 5]].to_sparse() - tm.assert_sp_series_equal(result, exp) - - # single element list (GH 15447) - result = sparse.loc[["A"]] - exp = orig.loc[["A"]].to_sparse() - tm.assert_sp_series_equal(result, exp) - - # dense array - result = sparse.loc[orig % 2 == 1] - exp = orig.loc[orig % 2 == 1].to_sparse() - tm.assert_sp_series_equal(result, exp) - - # sparse array (actuary it coerces to normal Series) - result = sparse.loc[sparse % 2 == 1] - exp = orig.loc[orig % 2 == 1].to_sparse() - tm.assert_sp_series_equal(result, exp) - - # sparse array - result = sparse.loc[pd.SparseArray(sparse % 2 == 1, dtype=bool)] - tm.assert_sp_series_equal(result, exp) - - def test_loc_multi_tuple(self): - orig = self.orig - sparse = self.sparse - - assert sparse.loc["C", 0] == orig.loc["C", 0] - assert np.isnan(sparse.loc["A", 1]) - assert np.isnan(sparse.loc["B", 0]) - - def test_loc_slice(self): - orig = self.orig - sparse = self.sparse - tm.assert_sp_series_equal(sparse.loc["A":], orig.loc["A":].to_sparse()) - tm.assert_sp_series_equal(sparse.loc["B":], orig.loc["B":].to_sparse()) - tm.assert_sp_series_equal(sparse.loc["C":], orig.loc["C":].to_sparse()) - - tm.assert_sp_series_equal(sparse.loc["A":"B"], orig.loc["A":"B"].to_sparse()) - tm.assert_sp_series_equal(sparse.loc[:"B"], orig.loc[:"B"].to_sparse()) - - def test_reindex(self): - # GH 15447 - orig = self.orig - sparse = self.sparse - - res = sparse.reindex([("A", 0), ("C", 1)]) - exp = orig.reindex([("A", 0), ("C", 1)]).to_sparse() - tm.assert_sp_series_equal(res, exp) - - # On specific level: - res = sparse.reindex(["A", "C", "B"], level=0) - exp = orig.reindex(["A", "C", "B"], level=0).to_sparse() - tm.assert_sp_series_equal(res, exp) - - # single element list (GH 15447) - res = sparse.reindex(["A"], level=0) - exp = orig.reindex(["A"], level=0).to_sparse() - tm.assert_sp_series_equal(res, exp) - - with pytest.raises(TypeError): - # Incomplete keys are not accepted for reindexing: - sparse.reindex(["A", "C"]) - - # "copy" argument: - res = sparse.reindex(sparse.index, copy=True) - exp = orig.reindex(orig.index, copy=True).to_sparse() - tm.assert_sp_series_equal(res, exp) - assert sparse is not res - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:DataFrame.to_sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") -class TestSparseDataFrameIndexing: - def test_getitem(self): - orig = pd.DataFrame( - [[1, np.nan, np.nan], [2, 3, np.nan], [np.nan, np.nan, 4], [0, np.nan, 5]], - columns=list("xyz"), - ) - sparse = orig.to_sparse() - - tm.assert_sp_series_equal(sparse["x"], orig["x"].to_sparse()) - tm.assert_sp_frame_equal(sparse[["x"]], orig[["x"]].to_sparse()) - tm.assert_sp_frame_equal(sparse[["z", "x"]], orig[["z", "x"]].to_sparse()) - - tm.assert_sp_frame_equal( - sparse[[True, False, True, True]], - orig[[True, False, True, True]].to_sparse(), - ) - - tm.assert_sp_frame_equal(sparse.iloc[[1, 2]], orig.iloc[[1, 2]].to_sparse()) - - def test_getitem_fill_value(self): - orig = pd.DataFrame( - [[1, np.nan, 0], [2, 3, np.nan], [0, np.nan, 4], [0, np.nan, 5]], - columns=list("xyz"), - ) - sparse = orig.to_sparse(fill_value=0) - - result = sparse[["z"]] - expected = orig[["z"]].to_sparse(fill_value=0) - tm.assert_sp_frame_equal(result, expected, check_fill_value=False) - - tm.assert_sp_series_equal(sparse["y"], orig["y"].to_sparse(fill_value=0)) - - exp = orig[["x"]].to_sparse(fill_value=0) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(sparse[["x"]], exp) - - exp = orig[["z", "x"]].to_sparse(fill_value=0) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(sparse[["z", "x"]], exp) - - indexer = [True, False, True, True] - exp = orig[indexer].to_sparse(fill_value=0) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(sparse[indexer], exp) - - exp = orig.iloc[[1, 2]].to_sparse(fill_value=0) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(sparse.iloc[[1, 2]], exp) - - def test_loc(self): - orig = pd.DataFrame( - [[1, np.nan, np.nan], [2, 3, np.nan], [np.nan, np.nan, 4]], - columns=list("xyz"), - ) - sparse = orig.to_sparse() - - assert sparse.loc[0, "x"] == 1 - assert np.isnan(sparse.loc[1, "z"]) - assert sparse.loc[2, "z"] == 4 - - # have to specify `kind='integer'`, since we construct a - # new SparseArray here, and the default sparse type is - # integer there, but block in SparseSeries - tm.assert_sp_series_equal(sparse.loc[0], orig.loc[0].to_sparse(kind="integer")) - tm.assert_sp_series_equal(sparse.loc[1], orig.loc[1].to_sparse(kind="integer")) - tm.assert_sp_series_equal( - sparse.loc[2, :], orig.loc[2, :].to_sparse(kind="integer") - ) - tm.assert_sp_series_equal( - sparse.loc[2, :], orig.loc[2, :].to_sparse(kind="integer") - ) - tm.assert_sp_series_equal(sparse.loc[:, "y"], orig.loc[:, "y"].to_sparse()) - tm.assert_sp_series_equal(sparse.loc[:, "y"], orig.loc[:, "y"].to_sparse()) - - result = sparse.loc[[1, 2]] - exp = orig.loc[[1, 2]].to_sparse() - tm.assert_sp_frame_equal(result, exp) - - result = sparse.loc[[1, 2], :] - exp = orig.loc[[1, 2], :].to_sparse() - tm.assert_sp_frame_equal(result, exp) - - result = sparse.loc[:, ["x", "z"]] - exp = orig.loc[:, ["x", "z"]].to_sparse() - tm.assert_sp_frame_equal(result, exp) - - result = sparse.loc[[0, 2], ["x", "z"]] - exp = orig.loc[[0, 2], ["x", "z"]].to_sparse() - tm.assert_sp_frame_equal(result, exp) - - # exceeds the bounds - result = sparse.reindex([1, 3, 4, 5]) - exp = orig.reindex([1, 3, 4, 5]).to_sparse() - tm.assert_sp_frame_equal(result, exp) - - # dense array - result = sparse.loc[orig.x % 2 == 1] - exp = orig.loc[orig.x % 2 == 1].to_sparse() - tm.assert_sp_frame_equal(result, exp) - - # sparse array (actuary it coerces to normal Series) - result = sparse.loc[sparse.x % 2 == 1] - exp = orig.loc[orig.x % 2 == 1].to_sparse() - tm.assert_sp_frame_equal(result, exp) - - # sparse array - result = sparse.loc[pd.SparseArray(sparse.x % 2 == 1, dtype=bool)] - tm.assert_sp_frame_equal(result, exp) - - def test_loc_index(self): - orig = pd.DataFrame( - [[1, np.nan, np.nan], [2, 3, np.nan], [np.nan, np.nan, 4]], - index=list("abc"), - columns=list("xyz"), - ) - sparse = orig.to_sparse() - - assert sparse.loc["a", "x"] == 1 - assert np.isnan(sparse.loc["b", "z"]) - assert sparse.loc["c", "z"] == 4 - - tm.assert_sp_series_equal( - sparse.loc["a"], orig.loc["a"].to_sparse(kind="integer") - ) - tm.assert_sp_series_equal( - sparse.loc["b"], orig.loc["b"].to_sparse(kind="integer") - ) - tm.assert_sp_series_equal( - sparse.loc["b", :], orig.loc["b", :].to_sparse(kind="integer") - ) - tm.assert_sp_series_equal( - sparse.loc["b", :], orig.loc["b", :].to_sparse(kind="integer") - ) - - tm.assert_sp_series_equal(sparse.loc[:, "z"], orig.loc[:, "z"].to_sparse()) - tm.assert_sp_series_equal(sparse.loc[:, "z"], orig.loc[:, "z"].to_sparse()) - - result = sparse.loc[["a", "b"]] - exp = orig.loc[["a", "b"]].to_sparse() - tm.assert_sp_frame_equal(result, exp) - - result = sparse.loc[["a", "b"], :] - exp = orig.loc[["a", "b"], :].to_sparse() - tm.assert_sp_frame_equal(result, exp) - - result = sparse.loc[:, ["x", "z"]] - exp = orig.loc[:, ["x", "z"]].to_sparse() - tm.assert_sp_frame_equal(result, exp) - - result = sparse.loc[["c", "a"], ["x", "z"]] - exp = orig.loc[["c", "a"], ["x", "z"]].to_sparse() - tm.assert_sp_frame_equal(result, exp) - - # dense array - result = sparse.loc[orig.x % 2 == 1] - exp = orig.loc[orig.x % 2 == 1].to_sparse() - tm.assert_sp_frame_equal(result, exp) - - # sparse array (actuary it coerces to normal Series) - result = sparse.loc[sparse.x % 2 == 1] - exp = orig.loc[orig.x % 2 == 1].to_sparse() - tm.assert_sp_frame_equal(result, exp) - - # sparse array - result = sparse.loc[pd.SparseArray(sparse.x % 2 == 1, dtype=bool)] - tm.assert_sp_frame_equal(result, exp) - - def test_loc_slice(self): - orig = pd.DataFrame( - [[1, np.nan, np.nan], [2, 3, np.nan], [np.nan, np.nan, 4]], - columns=list("xyz"), - ) - sparse = orig.to_sparse() - tm.assert_sp_frame_equal(sparse.loc[2:], orig.loc[2:].to_sparse()) - - def test_iloc(self): - orig = pd.DataFrame([[1, np.nan, np.nan], [2, 3, np.nan], [np.nan, np.nan, 4]]) - sparse = orig.to_sparse() - - assert sparse.iloc[1, 1] == 3 - assert np.isnan(sparse.iloc[2, 0]) - - tm.assert_sp_series_equal(sparse.iloc[0], orig.loc[0].to_sparse(kind="integer")) - tm.assert_sp_series_equal(sparse.iloc[1], orig.loc[1].to_sparse(kind="integer")) - tm.assert_sp_series_equal( - sparse.iloc[2, :], orig.iloc[2, :].to_sparse(kind="integer") - ) - tm.assert_sp_series_equal( - sparse.iloc[2, :], orig.iloc[2, :].to_sparse(kind="integer") - ) - tm.assert_sp_series_equal(sparse.iloc[:, 1], orig.iloc[:, 1].to_sparse()) - tm.assert_sp_series_equal(sparse.iloc[:, 1], orig.iloc[:, 1].to_sparse()) - - result = sparse.iloc[[1, 2]] - exp = orig.iloc[[1, 2]].to_sparse() - tm.assert_sp_frame_equal(result, exp) - - result = sparse.iloc[[1, 2], :] - exp = orig.iloc[[1, 2], :].to_sparse() - tm.assert_sp_frame_equal(result, exp) - - result = sparse.iloc[:, [1, 0]] - exp = orig.iloc[:, [1, 0]].to_sparse() - tm.assert_sp_frame_equal(result, exp) - - result = sparse.iloc[[2], [1, 0]] - exp = orig.iloc[[2], [1, 0]].to_sparse() - tm.assert_sp_frame_equal(result, exp) - - with pytest.raises(IndexError): - sparse.iloc[[1, 3, 5]] - - def test_iloc_slice(self): - orig = pd.DataFrame( - [[1, np.nan, np.nan], [2, 3, np.nan], [np.nan, np.nan, 4]], - columns=list("xyz"), - ) - sparse = orig.to_sparse() - tm.assert_sp_frame_equal(sparse.iloc[2:], orig.iloc[2:].to_sparse()) - - def test_at(self): - orig = pd.DataFrame( - [[1, np.nan, 0], [2, 3, np.nan], [0, np.nan, 4], [0, np.nan, 5]], - index=list("ABCD"), - columns=list("xyz"), - ) - sparse = orig.to_sparse() - assert sparse.at["A", "x"] == orig.at["A", "x"] - assert np.isnan(sparse.at["B", "z"]) - assert np.isnan(sparse.at["C", "y"]) - assert sparse.at["D", "x"] == orig.at["D", "x"] - - def test_at_fill_value(self): - orig = pd.DataFrame( - [[1, np.nan, 0], [2, 3, np.nan], [0, np.nan, 4], [0, np.nan, 5]], - index=list("ABCD"), - columns=list("xyz"), - ) - sparse = orig.to_sparse(fill_value=0) - assert sparse.at["A", "x"] == orig.at["A", "x"] - assert np.isnan(sparse.at["B", "z"]) - assert np.isnan(sparse.at["C", "y"]) - assert sparse.at["D", "x"] == orig.at["D", "x"] - - def test_iat(self): - orig = pd.DataFrame( - [[1, np.nan, 0], [2, 3, np.nan], [0, np.nan, 4], [0, np.nan, 5]], - index=list("ABCD"), - columns=list("xyz"), - ) - sparse = orig.to_sparse() - assert sparse.iat[0, 0] == orig.iat[0, 0] - assert np.isnan(sparse.iat[1, 2]) - assert np.isnan(sparse.iat[2, 1]) - assert sparse.iat[2, 0] == orig.iat[2, 0] - - assert np.isnan(sparse.iat[-1, -2]) - assert sparse.iat[-1, -1] == orig.iat[-1, -1] - - def test_iat_fill_value(self): - orig = pd.DataFrame( - [[1, np.nan, 0], [2, 3, np.nan], [0, np.nan, 4], [0, np.nan, 5]], - index=list("ABCD"), - columns=list("xyz"), - ) - sparse = orig.to_sparse(fill_value=0) - assert sparse.iat[0, 0] == orig.iat[0, 0] - assert np.isnan(sparse.iat[1, 2]) - assert np.isnan(sparse.iat[2, 1]) - assert sparse.iat[2, 0] == orig.iat[2, 0] - - assert np.isnan(sparse.iat[-1, -2]) - assert sparse.iat[-1, -1] == orig.iat[-1, -1] - - def test_take(self): - orig = pd.DataFrame( - [[1, np.nan, 0], [2, 3, np.nan], [0, np.nan, 4], [0, np.nan, 5]], - columns=list("xyz"), - ) - sparse = orig.to_sparse() - - tm.assert_sp_frame_equal(sparse.take([0]), orig.take([0]).to_sparse()) - tm.assert_sp_frame_equal(sparse.take([0, 1]), orig.take([0, 1]).to_sparse()) - tm.assert_sp_frame_equal(sparse.take([-1, -2]), orig.take([-1, -2]).to_sparse()) - - def test_take_fill_value(self): - orig = pd.DataFrame( - [[1, np.nan, 0], [2, 3, np.nan], [0, np.nan, 4], [0, np.nan, 5]], - columns=list("xyz"), - ) - sparse = orig.to_sparse(fill_value=0) - - exp = orig.take([0]).to_sparse(fill_value=0) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(sparse.take([0]), exp) - - exp = orig.take([0, 1]).to_sparse(fill_value=0) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(sparse.take([0, 1]), exp) - - exp = orig.take([-1, -2]).to_sparse(fill_value=0) - exp._default_fill_value = np.nan - tm.assert_sp_frame_equal(sparse.take([-1, -2]), exp) - - def test_reindex(self): - orig = pd.DataFrame( - [[1, np.nan, 0], [2, 3, np.nan], [0, np.nan, 4], [0, np.nan, 5]], - index=list("ABCD"), - columns=list("xyz"), - ) - sparse = orig.to_sparse() - - res = sparse.reindex(["A", "C", "B"]) - exp = orig.reindex(["A", "C", "B"]).to_sparse() - tm.assert_sp_frame_equal(res, exp) - - orig = pd.DataFrame( - [ - [np.nan, np.nan, np.nan], - [np.nan, np.nan, np.nan], - [np.nan, np.nan, np.nan], - [np.nan, np.nan, np.nan], - ], - index=list("ABCD"), - columns=list("xyz"), - ) - sparse = orig.to_sparse() - - res = sparse.reindex(["A", "C", "B"]) - exp = orig.reindex(["A", "C", "B"]).to_sparse() - tm.assert_sp_frame_equal(res, exp) - - def test_reindex_fill_value(self): - orig = pd.DataFrame( - [[1, np.nan, 0], [2, 3, np.nan], [0, np.nan, 4], [0, np.nan, 5]], - index=list("ABCD"), - columns=list("xyz"), - ) - sparse = orig.to_sparse(fill_value=0) - - res = sparse.reindex(["A", "C", "B"]) - exp = orig.reindex(["A", "C", "B"]).to_sparse(fill_value=0) - tm.assert_sp_frame_equal(res, exp) - - # all missing - orig = pd.DataFrame( - [ - [np.nan, np.nan, np.nan], - [np.nan, np.nan, np.nan], - [np.nan, np.nan, np.nan], - [np.nan, np.nan, np.nan], - ], - index=list("ABCD"), - columns=list("xyz"), - ) - sparse = orig.to_sparse(fill_value=0) - - res = sparse.reindex(["A", "C", "B"]) - exp = orig.reindex(["A", "C", "B"]).to_sparse(fill_value=0) - tm.assert_sp_frame_equal(res, exp) - - # all fill_value - orig = pd.DataFrame( - [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], - index=list("ABCD"), - columns=list("xyz"), - dtype=np.int, - ) - sparse = orig.to_sparse(fill_value=0) - - res = sparse.reindex(["A", "C", "B"]) - exp = orig.reindex(["A", "C", "B"]).to_sparse(fill_value=0) - tm.assert_sp_frame_equal(res, exp) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -class TestMultitype: - def setup_method(self, method): - self.cols = ["string", "int", "float", "object"] - - self.string_series = pd.SparseSeries(["a", "b", "c"]) - self.int_series = pd.SparseSeries([1, 2, 3]) - self.float_series = pd.SparseSeries([1.1, 1.2, 1.3]) - self.object_series = pd.SparseSeries([[], {}, set()]) - self.sdf = pd.SparseDataFrame( - { - "string": self.string_series, - "int": self.int_series, - "float": self.float_series, - "object": self.object_series, - } - ) - self.sdf = self.sdf[self.cols] - self.ss = pd.SparseSeries(["a", 1, 1.1, []], index=self.cols) - - def test_frame_basic_dtypes(self): - for _, row in self.sdf.iterrows(): - assert row.dtype == SparseDtype(object) - tm.assert_sp_series_equal( - self.sdf["string"], self.string_series, check_names=False - ) - tm.assert_sp_series_equal(self.sdf["int"], self.int_series, check_names=False) - tm.assert_sp_series_equal( - self.sdf["float"], self.float_series, check_names=False - ) - tm.assert_sp_series_equal( - self.sdf["object"], self.object_series, check_names=False - ) - - def test_frame_indexing_single(self): - tm.assert_sp_series_equal( - self.sdf.iloc[0], - pd.SparseSeries(["a", 1, 1.1, []], index=self.cols), - check_names=False, - ) - tm.assert_sp_series_equal( - self.sdf.iloc[1], - pd.SparseSeries(["b", 2, 1.2, {}], index=self.cols), - check_names=False, - ) - tm.assert_sp_series_equal( - self.sdf.iloc[2], - pd.SparseSeries(["c", 3, 1.3, set()], index=self.cols), - check_names=False, - ) - - def test_frame_indexing_multiple(self): - tm.assert_sp_frame_equal(self.sdf, self.sdf[:]) - tm.assert_sp_frame_equal(self.sdf, self.sdf.loc[:]) - tm.assert_sp_frame_equal( - self.sdf.iloc[[1, 2]], - pd.SparseDataFrame( - { - "string": self.string_series.iloc[[1, 2]], - "int": self.int_series.iloc[[1, 2]], - "float": self.float_series.iloc[[1, 2]], - "object": self.object_series.iloc[[1, 2]], - }, - index=[1, 2], - )[self.cols], - ) - tm.assert_sp_frame_equal( - self.sdf[["int", "string"]], - pd.SparseDataFrame({"int": self.int_series, "string": self.string_series}), - ) - - def test_series_indexing_single(self): - for i, idx in enumerate(self.cols): - assert self.ss.iloc[i] == self.ss[idx] - tm.assert_class_equal(self.ss.iloc[i], self.ss[idx], obj="series index") - - assert self.ss["string"] == "a" - assert self.ss["int"] == 1 - assert self.ss["float"] == 1.1 - assert self.ss["object"] == [] - - def test_series_indexing_multiple(self): - tm.assert_sp_series_equal( - self.ss.loc[["string", "int"]], - pd.SparseSeries(["a", 1], index=["string", "int"]), - ) - tm.assert_sp_series_equal( - self.ss.loc[["string", "object"]], - pd.SparseSeries(["a", []], index=["string", "object"]), - ) diff --git a/pandas/tests/sparse/test_pivot.py b/pandas/tests/sparse/test_pivot.py index 880c1c55f9f79..e69de29bb2d1d 100644 --- a/pandas/tests/sparse/test_pivot.py +++ b/pandas/tests/sparse/test_pivot.py @@ -1,59 +0,0 @@ -import numpy as np -import pytest - -import pandas as pd -import pandas.util.testing as tm - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:DataFrame.to_sparse:FutureWarning") -class TestPivotTable: - def setup_method(self, method): - rs = np.random.RandomState(0) - self.dense = pd.DataFrame( - { - "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"], - "B": ["one", "one", "two", "three", "two", "two", "one", "three"], - "C": rs.randn(8), - "D": rs.randn(8), - "E": [np.nan, np.nan, 1, 2, np.nan, 1, np.nan, np.nan], - } - ) - self.sparse = self.dense.to_sparse() - - def test_pivot_table(self): - res_sparse = pd.pivot_table(self.sparse, index="A", columns="B", values="C") - res_dense = pd.pivot_table(self.dense, index="A", columns="B", values="C") - tm.assert_frame_equal(res_sparse, res_dense) - - res_sparse = pd.pivot_table(self.sparse, index="A", columns="B", values="E") - res_dense = pd.pivot_table(self.dense, index="A", columns="B", values="E") - tm.assert_frame_equal(res_sparse, res_dense) - - res_sparse = pd.pivot_table( - self.sparse, index="A", columns="B", values="E", aggfunc="mean" - ) - res_dense = pd.pivot_table( - self.dense, index="A", columns="B", values="E", aggfunc="mean" - ) - tm.assert_frame_equal(res_sparse, res_dense) - - def test_pivot_table_with_nans(self): - res_sparse = pd.pivot_table( - self.sparse, index="A", columns="B", values="E", aggfunc="sum" - ) - res_dense = pd.pivot_table( - self.dense, index="A", columns="B", values="E", aggfunc="sum" - ) - tm.assert_frame_equal(res_sparse, res_dense) - - def test_pivot_table_multi(self): - res_sparse = pd.pivot_table( - self.sparse, index="A", columns="B", values=["D", "E"] - ) - res_dense = pd.pivot_table( - self.dense, index="A", columns="B", values=["D", "E"] - ) - res_dense = res_dense.apply(lambda x: x.astype("Sparse[float64]")) - tm.assert_frame_equal(res_sparse, res_dense) diff --git a/pandas/tests/sparse/test_reshape.py b/pandas/tests/sparse/test_reshape.py index bb5232f065a04..e69de29bb2d1d 100644 --- a/pandas/tests/sparse/test_reshape.py +++ b/pandas/tests/sparse/test_reshape.py @@ -1,43 +0,0 @@ -import numpy as np -import pytest - -import pandas as pd -import pandas.util.testing as tm - - -@pytest.fixture -def sparse_df(): - return pd.SparseDataFrame({0: {0: 1}, 1: {1: 1}, 2: {2: 1}}) # eye - - -@pytest.fixture -def multi_index3(): - return pd.MultiIndex.from_tuples([(0, 0), (1, 1), (2, 2)]) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -def test_sparse_frame_stack(sparse_df, multi_index3): - ss = sparse_df.stack() - expected = pd.SparseSeries(np.ones(3), index=multi_index3) - tm.assert_sp_series_equal(ss, expected) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -def test_sparse_frame_unstack(sparse_df): - mi = pd.MultiIndex.from_tuples([(0, 0), (1, 0), (1, 2)]) - sparse_df.index = mi - arr = np.array([[1, np.nan, np.nan], [np.nan, 1, np.nan], [np.nan, np.nan, 1]]) - unstacked_df = pd.DataFrame(arr, index=mi).unstack() - unstacked_sdf = sparse_df.unstack() - - tm.assert_numpy_array_equal(unstacked_df.values, unstacked_sdf.values) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -def test_sparse_series_unstack(sparse_df, multi_index3): - frame = pd.SparseSeries(np.ones(3), index=multi_index3).unstack() - - arr = np.array([1, np.nan, np.nan]) - arrays = {i: pd.SparseArray(np.roll(arr, i)) for i in range(3)} - expected = pd.DataFrame(arrays) - tm.assert_frame_equal(frame, expected) diff --git a/pandas/util/testing.py b/pandas/util/testing.py index c54dab046f57e..6330e13a7e15e 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -2875,30 +2875,6 @@ def _constructor_sliced(self): return SubclassedSeries -class SubclassedSparseSeries(pd.SparseSeries): - _metadata = ["testattr"] - - @property - def _constructor(self): - return SubclassedSparseSeries - - @property - def _constructor_expanddim(self): - return SubclassedSparseDataFrame - - -class SubclassedSparseDataFrame(pd.SparseDataFrame): - _metadata = ["testattr"] - - @property - def _constructor(self): - return SubclassedSparseDataFrame - - @property - def _constructor_sliced(self): - return SubclassedSparseSeries - - class SubclassedCategorical(Categorical): @property def _constructor(self): From 5418dd5339d90dbed77766648d34d06a4c45dbde Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 12 Sep 2019 16:51:29 -0500 Subject: [PATCH 02/33] round 2 --- doc/source/reference/frame.rst | 7 -- doc/source/reference/series.rst | 10 -- doc/source/user_guide/sparse.rst | 20 +--- pandas/_typing.py | 5 +- pandas/core/arrays/sparse.py | 4 +- pandas/core/dtypes/common.py | 17 +--- pandas/core/frame.py | 76 --------------- pandas/core/generic.py | 11 --- pandas/core/ops/methods.py | 1 - pandas/core/reshape/concat.py | 3 +- pandas/core/reshape/reshape.py | 2 +- pandas/core/series.py | 38 +------- pandas/core/sparse/scipy_sparse.py | 16 +--- pandas/tests/api/test_api.py | 2 - pandas/tests/frame/test_api.py | 4 - pandas/tests/frame/test_subclass.py | 32 ------- pandas/tests/io/test_pickle.py | 8 -- pandas/tests/series/test_subclass.py | 69 -------------- pandas/util/testing.py | 138 --------------------------- 19 files changed, 16 insertions(+), 447 deletions(-) diff --git a/doc/source/reference/frame.rst b/doc/source/reference/frame.rst index b1c6172fb1261..ccbeb7a100b09 100644 --- a/doc/source/reference/frame.rst +++ b/doc/source/reference/frame.rst @@ -361,10 +361,3 @@ Serialization / IO / conversion DataFrame.to_string DataFrame.to_clipboard DataFrame.style - -Sparse -~~~~~~ -.. autosummary:: - :toctree: api/ - - SparseDataFrame.to_coo diff --git a/doc/source/reference/series.rst b/doc/source/reference/series.rst index 7ba625c141f24..bef6b10be3fff 100644 --- a/doc/source/reference/series.rst +++ b/doc/source/reference/series.rst @@ -581,13 +581,3 @@ Serialization / IO / conversion Series.to_string Series.to_clipboard Series.to_latex - - -Sparse ------- - -.. autosummary:: - :toctree: api/ - - SparseSeries.to_coo - SparseSeries.from_coo diff --git a/doc/source/user_guide/sparse.rst b/doc/source/user_guide/sparse.rst index 98fd30f67d05b..3ea0d02da9367 100644 --- a/doc/source/user_guide/sparse.rst +++ b/doc/source/user_guide/sparse.rst @@ -6,12 +6,6 @@ Sparse data structures ********************** -.. note:: - - ``SparseSeries`` and ``SparseDataFrame`` have been deprecated. Their purpose - is served equally well by a :class:`Series` or :class:`DataFrame` with - sparse values. See :ref:`sparse.migration` for tips on migrating. - Pandas provides data structures for efficiently storing sparse data. These are not necessarily sparse in the typical "mostly 0". Rather, you can view these objects as being "compressed" where any data matching a specific value (``NaN`` / missing value, though any value @@ -168,6 +162,11 @@ the correct dense result. Migrating --------- +.. note:: + + ``SparseSeries`` and ``SparseDataFrame`` were removed in pandas 1.0.0. This migration + guide is present to aid in migrating from previous versions. + In older versions of pandas, the ``SparseSeries`` and ``SparseDataFrame`` classes (documented below) were the preferred way to work with sparse data. With the advent of extension arrays, these subclasses are no longer needed. Their purpose is better served by using a regular Series or DataFrame with @@ -366,12 +365,3 @@ row and columns coordinates of the matrix. Note that this will consume a signifi ss_dense = pd.Series.sparse.from_coo(A, dense_index=True) ss_dense - - -.. _sparse.subclasses: - -Sparse subclasses ------------------ - -The :class:`SparseSeries` and :class:`SparseDataFrame` classes are deprecated. Visit their -API pages for usage. diff --git a/pandas/_typing.py b/pandas/_typing.py index 37a5d7945955d..db9e73042696e 100644 --- a/pandas/_typing.py +++ b/pandas/_typing.py @@ -12,13 +12,10 @@ from pandas.core.dtypes.dtypes import ExtensionDtype # noqa: F401 from pandas.core.indexes.base import Index # noqa: F401 from pandas.core.series import Series # noqa: F401 - from pandas.core.sparse.series import SparseSeries # noqa: F401 from pandas.core.generic import NDFrame # noqa: F401 -AnyArrayLike = TypeVar( - "AnyArrayLike", "ExtensionArray", "Index", "Series", "SparseSeries", np.ndarray -) +AnyArrayLike = TypeVar("AnyArrayLike", "ExtensionArray", "Index", "Series", np.ndarray) ArrayLike = TypeVar("ArrayLike", "ExtensionArray", np.ndarray) DatetimeLikeScalar = TypeVar("DatetimeLikeScalar", "Period", "Timestamp", "Timedelta") Dtype = Union[str, np.dtype, "ExtensionDtype"] diff --git a/pandas/core/arrays/sparse.py b/pandas/core/arrays/sparse.py index 0ada8035b4de5..8c7339aea0a45 100644 --- a/pandas/core/arrays/sparse.py +++ b/pandas/core/arrays/sparse.py @@ -1968,7 +1968,7 @@ def _delegate_method(self, name, *args, **kwargs): @classmethod def from_coo(cls, A, dense_index=False): """ - Create a SparseSeries from a scipy.sparse.coo_matrix. + Create a Series with sparse values from a scipy.sparse.coo_matrix. Parameters ---------- @@ -2009,7 +2009,7 @@ def from_coo(cls, A, dense_index=False): from pandas.core.sparse.scipy_sparse import _coo_to_sparse_series from pandas import Series - result = _coo_to_sparse_series(A, dense_index=dense_index, sparse_series=False) + result = _coo_to_sparse_series(A, dense_index=dense_index) result = Series(result.array, index=result.index, copy=False) return result diff --git a/pandas/core/dtypes/common.py b/pandas/core/dtypes/common.py index f75493be2dab1..7e420cb05027a 100644 --- a/pandas/core/dtypes/common.py +++ b/pandas/core/dtypes/common.py @@ -273,8 +273,6 @@ def is_sparse(arr): See Also -------- - DataFrame.to_sparse : Convert DataFrame to a SparseDataFrame. - Series.to_sparse : Convert Series to SparseSeries. Series.to_dense : Return dense representation of a Series. Examples @@ -283,7 +281,7 @@ def is_sparse(arr): >>> is_sparse(pd.SparseArray([0, 0, 1, 0])) True - >>> is_sparse(pd.SparseSeries([0, 0, 1, 0])) + >>> is_sparse(pd.Series(pd.SparseArray([0, 0, 1, 0]))) True Returns `False` if the parameter is not sparse. @@ -300,14 +298,6 @@ def is_sparse(arr): False Returns `False` if the parameter has more than one dimension. - - >>> df = pd.SparseDataFrame([389., 24., 80.5, np.nan], - columns=['max_speed'], - index=['falcon', 'parrot', 'lion', 'monkey']) - >>> is_sparse(df) - False - >>> is_sparse(df.max_speed) - True """ from pandas.core.arrays.sparse import SparseDtype @@ -340,8 +330,6 @@ def is_scipy_sparse(arr): True >>> is_scipy_sparse(pd.SparseArray([1, 2, 3])) False - >>> is_scipy_sparse(pd.SparseSeries([1, 2, 3])) - False """ global _is_scipy_sparse @@ -1715,9 +1703,6 @@ def is_extension_type(arr): True >>> is_extension_type(pd.SparseArray([1, 2, 3])) True - >>> is_extension_type(pd.SparseSeries([1, 2, 3])) - True - >>> >>> from scipy.sparse import bsr_matrix >>> is_extension_type(bsr_matrix([1, 2, 3])) False diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 16fece1c7eb8b..6165817a36542 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -1925,81 +1925,6 @@ def _from_arrays(cls, arrays, columns, index, dtype=None): mgr = arrays_to_mgr(arrays, columns, index, columns, dtype=dtype) return cls(mgr) - def to_sparse(self, fill_value=None, kind="block"): - """ - Convert to SparseDataFrame. - - .. deprecated:: 0.25.0 - - Implement the sparse version of the DataFrame meaning that any data - matching a specific value it's omitted in the representation. - The sparse DataFrame allows for a more efficient storage. - - Parameters - ---------- - fill_value : float, default None - The specific value that should be omitted in the representation. - kind : {'block', 'integer'}, default 'block' - The kind of the SparseIndex tracking where data is not equal to - the fill value: - - - 'block' tracks only the locations and sizes of blocks of data. - - 'integer' keeps an array with all the locations of the data. - - In most cases 'block' is recommended, since it's more memory - efficient. - - Returns - ------- - SparseDataFrame - The sparse representation of the DataFrame. - - See Also - -------- - DataFrame.to_dense : - Converts the DataFrame back to the its dense form. - - Examples - -------- - >>> df = pd.DataFrame([(np.nan, np.nan), - ... (1., np.nan), - ... (np.nan, 1.)]) - >>> df - 0 1 - 0 NaN NaN - 1 1.0 NaN - 2 NaN 1.0 - >>> type(df) - - - >>> sdf = df.to_sparse() # doctest: +SKIP - >>> sdf # doctest: +SKIP - 0 1 - 0 NaN NaN - 1 1.0 NaN - 2 NaN 1.0 - >>> type(sdf) # doctest: +SKIP - - """ - warnings.warn( - "DataFrame.to_sparse is deprecated and will be removed " - "in a future version", - FutureWarning, - stacklevel=2, - ) - - from pandas.core.sparse.api import SparseDataFrame - - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", message="SparseDataFrame") - return SparseDataFrame( - self._series, - index=self.index, - columns=self.columns, - default_kind=kind, - default_fill_value=fill_value, - ) - @deprecate_kwarg(old_arg_name="encoding", new_arg_name=None) def to_stata( self, @@ -7192,7 +7117,6 @@ def join(self, other, on=None, how="left", lsuffix="", rsuffix="", sort=False): 4 K4 A4 NaN 5 K5 A5 NaN """ - # For SparseDataFrame's benefit return self._join_compat( other, on=on, how=how, lsuffix=lsuffix, rsuffix=rsuffix, sort=sort ) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 68308b2f83b60..7f67174aade4b 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -5575,9 +5575,6 @@ def get_ftype_counts(self): .. deprecated:: 0.23.0 - This is useful for SparseDataFrame or for DataFrames containing - sparse arrays. - Returns ------- dtype : Series @@ -5672,7 +5669,6 @@ def ftypes(self): See Also -------- DataFrame.dtypes: Series with just dtype information. - SparseDataFrame : Container for sparse tabular data. Notes ----- @@ -5688,13 +5684,6 @@ def ftypes(self): 2 float64:dense 3 float64:dense dtype: object - - >>> pd.SparseDataFrame(arr).ftypes # doctest: +SKIP - 0 float64:sparse - 1 float64:sparse - 2 float64:sparse - 3 float64:sparse - dtype: object """ warnings.warn( "DataFrame.ftypes is deprecated and will " diff --git a/pandas/core/ops/methods.py b/pandas/core/ops/methods.py index 3dabb1075f03d..477c847fb01e6 100644 --- a/pandas/core/ops/methods.py +++ b/pandas/core/ops/methods.py @@ -61,7 +61,6 @@ def _get_method_wrappers(cls): comp_special = _comp_method_SERIES bool_special = _bool_method_SERIES elif issubclass(cls, ABCDataFrame): - # Same for DataFrame and SparseDataFrame arith_flex = _arith_method_FRAME comp_flex = _flex_comp_method_FRAME arith_special = _arith_method_FRAME diff --git a/pandas/core/reshape/concat.py b/pandas/core/reshape/concat.py index 8ae9a1253e79c..1b3502f1c8346 100644 --- a/pandas/core/reshape/concat.py +++ b/pandas/core/reshape/concat.py @@ -726,7 +726,6 @@ def _get_series_result_type(result, objs=None): def _get_frame_result_type(result, objs): """ return appropriate class of DataFrame-like concat - if all blocks are sparse, return SparseDataFrame - otherwise, return 1st obj """ + # TODO: just inline this return objs[0] diff --git a/pandas/core/reshape/reshape.py b/pandas/core/reshape/reshape.py index c32ca47c19160..e654685d24d9d 100644 --- a/pandas/core/reshape/reshape.py +++ b/pandas/core/reshape/reshape.py @@ -57,7 +57,7 @@ class _Unstacker: float and missing values will be set to NaN. constructor : object Pandas ``DataFrame`` or subclass used to create unstacked - response. If None, DataFrame or SparseDataFrame will be used. + response. If None, DataFrame will be used. Examples -------- diff --git a/pandas/core/series.py b/pandas/core/series.py index 676f9a50a4e8b..c602a419f29ef 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -55,7 +55,7 @@ import pandas as pd from pandas.core import algorithms, base, generic, nanops, ops from pandas.core.accessor import CachedAccessor -from pandas.core.arrays import ExtensionArray, SparseArray +from pandas.core.arrays import ExtensionArray from pandas.core.arrays.categorical import Categorical, CategoricalAccessor from pandas.core.arrays.sparse import SparseAccessor import pandas.core.common as com @@ -384,10 +384,6 @@ def from_array( FutureWarning, stacklevel=2, ) - if isinstance(arr, ABCSparseArray): - from pandas.core.sparse.series import SparseSeries - - cls = SparseSeries return cls( arr, index=index, name=name, dtype=dtype, copy=copy, fastpath=fastpath ) @@ -1776,38 +1772,6 @@ def to_frame(self, name=None): return df - def to_sparse(self, kind="block", fill_value=None): - """ - Convert Series to SparseSeries. - - .. deprecated:: 0.25.0 - - Parameters - ---------- - kind : {'block', 'integer'}, default 'block' - fill_value : float, defaults to NaN (missing) - Value to use for filling NaN values. - - Returns - ------- - SparseSeries - Sparse representation of the Series. - """ - - warnings.warn( - "Series.to_sparse is deprecated and will be removed in a future version", - FutureWarning, - stacklevel=2, - ) - from pandas.core.sparse.series import SparseSeries - - values = SparseArray(self, kind=kind, fill_value=fill_value) - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", message="SparseSeries") - return SparseSeries(values, index=self.index, name=self.name).__finalize__( - self - ) - def _set_name(self, name, inplace=False): """ Set the Series name. diff --git a/pandas/core/sparse/scipy_sparse.py b/pandas/core/sparse/scipy_sparse.py index e8d8996fdd6ad..11c27451a5801 100644 --- a/pandas/core/sparse/scipy_sparse.py +++ b/pandas/core/sparse/scipy_sparse.py @@ -1,7 +1,7 @@ """ Interaction with scipy.sparse matrices. -Currently only includes SparseSeries.to_coo helpers. +Currently only includes to_coo helpers. """ from collections import OrderedDict @@ -115,7 +115,7 @@ def _sparse_series_to_coo(ss, row_levels=(0,), column_levels=(1,), sort_labels=F return sparse_matrix, rows, columns -def _coo_to_sparse_series(A, dense_index: bool = False, sparse_series: bool = True): +def _coo_to_sparse_series(A, dense_index: bool = False): """ Convert a scipy.sparse.coo_matrix to a SparseSeries. @@ -123,16 +123,14 @@ def _coo_to_sparse_series(A, dense_index: bool = False, sparse_series: bool = Tr ---------- A : scipy.sparse.coo.coo_matrix dense_index : bool, default False - sparse_series : bool, default True Returns ------- - Series or SparseSeries + Series Raises ------ TypeError if A is not a coo_matrix - """ from pandas import SparseDtype @@ -141,13 +139,7 @@ def _coo_to_sparse_series(A, dense_index: bool = False, sparse_series: bool = Tr except AttributeError: raise TypeError("Expected coo_matrix. Got {} instead.".format(type(A).__name__)) s = s.sort_index() - if sparse_series: - # TODO(SparseSeries): remove this and the sparse_series keyword. - # This is just here to avoid a DeprecationWarning when - # _coo_to_sparse_series is called via Series.sparse.from_coo - s = s.to_sparse() # TODO: specify kind? - else: - s = s.astype(SparseDtype(s.dtype)) + s = s.astype(SparseDtype(s.dtype)) if dense_index: # is there a better constructor method to use here? i = range(A.shape[0]) diff --git a/pandas/tests/api/test_api.py b/pandas/tests/api/test_api.py index 326bef7f4b480..0958b9472d591 100644 --- a/pandas/tests/api/test_api.py +++ b/pandas/tests/api/test_api.py @@ -67,9 +67,7 @@ class TestPDApi(Base): "UInt64Index", "Series", "SparseArray", - "SparseDataFrame", "SparseDtype", - "SparseSeries", "Timedelta", "TimedeltaIndex", "Timestamp", diff --git a/pandas/tests/frame/test_api.py b/pandas/tests/frame/test_api.py index c6852576f660b..d53a3d81ab5f8 100644 --- a/pandas/tests/frame/test_api.py +++ b/pandas/tests/frame/test_api.py @@ -10,7 +10,6 @@ Categorical, DataFrame, Series, - SparseDataFrame, SparseDtype, compat, date_range, @@ -220,9 +219,6 @@ def test_iterrows(self, float_frame, float_string_frame): def test_iterrows_iso8601(self): # GH 19671 - if self.klass == SparseDataFrame: - pytest.xfail(reason="SparseBlock datetime type not implemented.") - s = self.klass( { "non_iso8601": ["M1701", "M1802", "M1903", "M2004"], diff --git a/pandas/tests/frame/test_subclass.py b/pandas/tests/frame/test_subclass.py index c66a97c2b294b..649a78b785d21 100644 --- a/pandas/tests/frame/test_subclass.py +++ b/pandas/tests/frame/test_subclass.py @@ -190,38 +190,6 @@ def test_subclass_iterrows(self): assert isinstance(row, tm.SubclassedSeries) tm.assert_series_equal(row, df.loc[i]) - @pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") - def test_subclass_sparse_slice(self): - rows = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]] - ssdf = tm.SubclassedSparseDataFrame(rows) - ssdf.testattr = "testattr" - - tm.assert_sp_frame_equal(ssdf.loc[:2], tm.SubclassedSparseDataFrame(rows[:3])) - tm.assert_sp_frame_equal(ssdf.iloc[:2], tm.SubclassedSparseDataFrame(rows[:2])) - tm.assert_sp_frame_equal(ssdf[:2], tm.SubclassedSparseDataFrame(rows[:2])) - assert ssdf.loc[:2].testattr == "testattr" - assert ssdf.iloc[:2].testattr == "testattr" - assert ssdf[:2].testattr == "testattr" - - tm.assert_sp_series_equal( - ssdf.loc[1], - tm.SubclassedSparseSeries(rows[1]), - check_names=False, - check_kind=False, - ) - tm.assert_sp_series_equal( - ssdf.iloc[1], - tm.SubclassedSparseSeries(rows[1]), - check_names=False, - check_kind=False, - ) - - @pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") - def test_subclass_sparse_transpose(self): - ossdf = tm.SubclassedSparseDataFrame([[1, 2, 3], [4, 5, 6]]) - essdf = tm.SubclassedSparseDataFrame([[1, 4], [2, 5], [3, 6]]) - tm.assert_sp_frame_equal(ossdf.T, essdf) - def test_subclass_stack(self): # GH 15564 df = tm.SubclassedDataFrame( diff --git a/pandas/tests/io/test_pickle.py b/pandas/tests/io/test_pickle.py index 30555508f0998..40075d0699487 100644 --- a/pandas/tests/io/test_pickle.py +++ b/pandas/tests/io/test_pickle.py @@ -82,10 +82,6 @@ def compare(data, vf, version): return data -def compare_sp_series_ts(res, exp, typ, version): - tm.assert_sp_series_equal(res, exp) - - def compare_series_ts(result, expected, typ, version): # GH 7748 tm.assert_series_equal(result, expected) @@ -134,10 +130,6 @@ def compare_index_period(result, expected, typ, version): tm.assert_index_equal(result.shift(2), expected.shift(2)) -def compare_sp_frame_float(result, expected, typ, version): - tm.assert_sp_frame_equal(result, expected) - - files = glob.glob( os.path.join(os.path.dirname(__file__), "data", "legacy_pickle", "*", "*.pickle") ) diff --git a/pandas/tests/series/test_subclass.py b/pandas/tests/series/test_subclass.py index 450fdc3f4dd6f..6b82f890e974b 100644 --- a/pandas/tests/series/test_subclass.py +++ b/pandas/tests/series/test_subclass.py @@ -1,8 +1,3 @@ -import numpy as np -import pytest - -import pandas as pd -from pandas import SparseDtype import pandas.util.testing as tm @@ -38,67 +33,3 @@ def test_subclass_unstack(self): def test_subclass_empty_repr(self): assert "SubclassedSeries" in repr(tm.SubclassedSeries()) - - -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -class TestSparseSeriesSubclassing: - def test_subclass_sparse_slice(self): - # int64 - s = tm.SubclassedSparseSeries([1, 2, 3, 4, 5]) - exp = tm.SubclassedSparseSeries([2, 3, 4], index=[1, 2, 3]) - tm.assert_sp_series_equal(s.loc[1:3], exp) - assert s.loc[1:3].dtype == SparseDtype(np.int64) - - exp = tm.SubclassedSparseSeries([2, 3], index=[1, 2]) - tm.assert_sp_series_equal(s.iloc[1:3], exp) - assert s.iloc[1:3].dtype == SparseDtype(np.int64) - - exp = tm.SubclassedSparseSeries([2, 3], index=[1, 2]) - tm.assert_sp_series_equal(s[1:3], exp) - assert s[1:3].dtype == SparseDtype(np.int64) - - # float64 - s = tm.SubclassedSparseSeries([1.0, 2.0, 3.0, 4.0, 5.0]) - exp = tm.SubclassedSparseSeries([2.0, 3.0, 4.0], index=[1, 2, 3]) - tm.assert_sp_series_equal(s.loc[1:3], exp) - assert s.loc[1:3].dtype == SparseDtype(np.float64) - - exp = tm.SubclassedSparseSeries([2.0, 3.0], index=[1, 2]) - tm.assert_sp_series_equal(s.iloc[1:3], exp) - assert s.iloc[1:3].dtype == SparseDtype(np.float64) - - exp = tm.SubclassedSparseSeries([2.0, 3.0], index=[1, 2]) - tm.assert_sp_series_equal(s[1:3], exp) - assert s[1:3].dtype == SparseDtype(np.float64) - - def test_subclass_sparse_addition(self): - s1 = tm.SubclassedSparseSeries([1, 3, 5]) - s2 = tm.SubclassedSparseSeries([-2, 5, 12]) - exp = tm.SubclassedSparseSeries([-1, 8, 17]) - tm.assert_sp_series_equal(s1 + s2, exp) - - s1 = tm.SubclassedSparseSeries([4.0, 5.0, 6.0]) - s2 = tm.SubclassedSparseSeries([1.0, 2.0, 3.0]) - exp = tm.SubclassedSparseSeries([5.0, 7.0, 9.0]) - tm.assert_sp_series_equal(s1 + s2, exp) - - def test_subclass_sparse_to_frame(self): - s = tm.SubclassedSparseSeries([1, 2], index=list("ab"), name="xxx") - res = s.to_frame() - - exp_arr = pd.SparseArray([1, 2], dtype=np.int64, kind="block", fill_value=0) - exp = tm.SubclassedSparseDataFrame( - {"xxx": exp_arr}, index=list("ab"), default_fill_value=0 - ) - tm.assert_sp_frame_equal(res, exp) - - # create from int dict - res = tm.SubclassedSparseDataFrame( - {"xxx": [1, 2]}, index=list("ab"), default_fill_value=0 - ) - tm.assert_sp_frame_equal(res, exp) - - s = tm.SubclassedSparseSeries([1.1, 2.1], index=list("ab"), name="xxx") - res = s.to_frame() - exp = tm.SubclassedSparseDataFrame({"xxx": [1.1, 2.1]}, index=list("ab")) - tm.assert_sp_frame_equal(res, exp) diff --git a/pandas/util/testing.py b/pandas/util/testing.py index 6330e13a7e15e..bbb00d81ff1ce 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -1332,8 +1332,6 @@ def assert_frame_equal( _check_isinstance(left, right, DataFrame) if check_frame_type: - # ToDo: There are some tests using rhs is SparseDataFrame - # lhs is DataFrame. Should use assert_class_equal in future assert isinstance(left, type(right)) # assert_class_equal(left, right, obj=obj) @@ -1557,142 +1555,6 @@ def assert_sp_array_equal( assert_numpy_array_equal(left.to_dense(), right.to_dense(), check_dtype=check_dtype) -def assert_sp_series_equal( - left, - right, - check_dtype=True, - exact_indices=True, - check_series_type=True, - check_names=True, - check_kind=True, - check_fill_value=True, - consolidate_block_indices=False, - obj="SparseSeries", -): - """Check that the left and right SparseSeries are equal. - - Parameters - ---------- - left : SparseSeries - right : SparseSeries - check_dtype : bool, default True - Whether to check the Series dtype is identical. - exact_indices : bool, default True - check_series_type : bool, default True - Whether to check the SparseSeries class is identical. - check_names : bool, default True - Whether to check the SparseSeries name attribute. - check_kind : bool, default True - Whether to just the kind of the sparse index for each column. - check_fill_value : bool, default True - Whether to check that left.fill_value matches right.fill_value - consolidate_block_indices : bool, default False - Whether to consolidate contiguous blocks for sparse arrays with - a BlockIndex. Some operations, e.g. concat, will end up with - block indices that could be consolidated. Setting this to true will - create a new BlockIndex for that array, with consolidated - block indices. - obj : str, default 'SparseSeries' - Specify the object name being compared, internally used to show - the appropriate assertion message. - """ - _check_isinstance(left, right, pd.SparseSeries) - - if check_series_type: - assert_class_equal(left, right, obj=obj) - - assert_index_equal(left.index, right.index, obj="{obj}.index".format(obj=obj)) - - assert_sp_array_equal( - left.values, - right.values, - check_kind=check_kind, - check_fill_value=check_fill_value, - consolidate_block_indices=consolidate_block_indices, - ) - - if check_names: - assert_attr_equal("name", left, right) - if check_dtype: - assert_attr_equal("dtype", left, right) - - assert_numpy_array_equal(np.asarray(left.values), np.asarray(right.values)) - - -def assert_sp_frame_equal( - left, - right, - check_dtype=True, - exact_indices=True, - check_frame_type=True, - check_kind=True, - check_fill_value=True, - consolidate_block_indices=False, - obj="SparseDataFrame", -): - """Check that the left and right SparseDataFrame are equal. - - Parameters - ---------- - left : SparseDataFrame - right : SparseDataFrame - check_dtype : bool, default True - Whether to check the Series dtype is identical. - exact_indices : bool, default True - SparseSeries SparseIndex objects must be exactly the same, - otherwise just compare dense representations. - check_frame_type : bool, default True - Whether to check the SparseDataFrame class is identical. - check_kind : bool, default True - Whether to just the kind of the sparse index for each column. - check_fill_value : bool, default True - Whether to check that left.fill_value matches right.fill_value - consolidate_block_indices : bool, default False - Whether to consolidate contiguous blocks for sparse arrays with - a BlockIndex. Some operations, e.g. concat, will end up with - block indices that could be consolidated. Setting this to true will - create a new BlockIndex for that array, with consolidated - block indices. - obj : str, default 'SparseDataFrame' - Specify the object name being compared, internally used to show - the appropriate assertion message. - """ - _check_isinstance(left, right, pd.SparseDataFrame) - - if check_frame_type: - assert_class_equal(left, right, obj=obj) - - assert_index_equal(left.index, right.index, obj="{obj}.index".format(obj=obj)) - assert_index_equal(left.columns, right.columns, obj="{obj}.columns".format(obj=obj)) - - if check_fill_value: - assert_attr_equal("default_fill_value", left, right, obj=obj) - - for col, series in left.items(): - assert col in right - # trade-off? - - if exact_indices: - assert_sp_series_equal( - series, - right[col], - check_dtype=check_dtype, - check_kind=check_kind, - check_fill_value=check_fill_value, - consolidate_block_indices=consolidate_block_indices, - ) - else: - assert_series_equal( - series.to_dense(), right[col].to_dense(), check_dtype=check_dtype - ) - - # do I care? - # assert(left.default_kind == right.default_kind) - - for col in right: - assert col in left - - # ----------------------------------------------------------------------------- # Others From f61b5e36f1346a9644e18d93173fbdd63c041296 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 12 Sep 2019 21:51:58 -0500 Subject: [PATCH 03/33] Round 3 --- pandas/tests/dtypes/test_common.py | 20 -------------------- pandas/tests/dtypes/test_generic.py | 7 +------ pandas/tests/io/json/test_pandas.py | 19 ------------------- 3 files changed, 1 insertion(+), 45 deletions(-) diff --git a/pandas/tests/dtypes/test_common.py b/pandas/tests/dtypes/test_common.py index 36548f3515a48..f5d31eb3eb4aa 100644 --- a/pandas/tests/dtypes/test_common.py +++ b/pandas/tests/dtypes/test_common.py @@ -21,7 +21,6 @@ UNSIGNED_EA_INT_DTYPES, UNSIGNED_INT_DTYPES, ) -from pandas.core.sparse.api import SparseDtype import pandas.util.testing as tm ignore_sparse_warning = pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") @@ -182,7 +181,6 @@ def test_is_object(): @ignore_sparse_warning def test_is_sparse(check_scipy): assert com.is_sparse(pd.SparseArray([1, 2, 3])) - assert com.is_sparse(pd.SparseSeries([1, 2, 3])) assert not com.is_sparse(np.array([1, 2, 3])) @@ -200,7 +198,6 @@ def test_is_scipy_sparse(): assert com.is_scipy_sparse(bsr_matrix([1, 2, 3])) assert not com.is_scipy_sparse(pd.SparseArray([1, 2, 3])) - assert not com.is_scipy_sparse(pd.SparseSeries([1, 2, 3])) def test_is_categorical(): @@ -596,7 +593,6 @@ def test_is_extension_type(check_scipy): assert com.is_extension_type(cat) assert com.is_extension_type(pd.Series(cat)) assert com.is_extension_type(pd.SparseArray([1, 2, 3])) - assert com.is_extension_type(pd.SparseSeries([1, 2, 3])) assert com.is_extension_type(pd.DatetimeIndex(["2000"], tz="US/Eastern")) dtype = DatetimeTZDtype("ns", tz="US/Eastern") @@ -664,14 +660,6 @@ def test__get_dtype(input_param, result): assert com._get_dtype(input_param) == result -@ignore_sparse_warning -def test__get_dtype_sparse(): - ser = pd.SparseSeries([1, 2], dtype="int32") - expected = SparseDtype("int32") - assert com._get_dtype(ser) == expected - assert com._get_dtype(ser.dtype) == expected - - @pytest.mark.parametrize( "input_param,expected_error_message", [ @@ -723,11 +711,3 @@ def test__get_dtype_fails(input_param, expected_error_message): ) def test__is_dtype_type(input_param, result): assert com._is_dtype_type(input_param, lambda tipo: tipo == result) - - -@ignore_sparse_warning -def test__is_dtype_type_sparse(): - ser = pd.SparseSeries([1, 2], dtype="int32") - result = np.dtype("int32") - assert com._is_dtype_type(ser, lambda tipo: tipo == result) - assert com._is_dtype_type(ser.dtype, lambda tipo: tipo == result) diff --git a/pandas/tests/dtypes/test_generic.py b/pandas/tests/dtypes/test_generic.py index a4c7965bf7554..471fd06a29ae9 100644 --- a/pandas/tests/dtypes/test_generic.py +++ b/pandas/tests/dtypes/test_generic.py @@ -1,4 +1,4 @@ -from warnings import catch_warnings, simplefilter +from warnings import catch_warnings import numpy as np @@ -17,11 +17,6 @@ class TestABCClasses: categorical = pd.Categorical([1, 2, 3], categories=[2, 3, 1]) categorical_df = pd.DataFrame({"values": [1, 2, 3]}, index=categorical) df = pd.DataFrame({"names": ["a", "b", "c"]}, index=multi_index) - with catch_warnings(): - simplefilter("ignore", FutureWarning) - sparse_series = pd.Series([1, 2, 3]).to_sparse() - sparse_frame = pd.SparseDataFrame({"a": [1, -1, None]}) - sparse_array = pd.SparseArray(np.random.randn(10)) datetime_array = pd.core.arrays.DatetimeArray(datetime_index) timedelta_array = pd.core.arrays.TimedeltaArray(timedelta_index) diff --git a/pandas/tests/io/json/test_pandas.py b/pandas/tests/io/json/test_pandas.py index 9842a706f43d7..11df2b85086b6 100644 --- a/pandas/tests/io/json/test_pandas.py +++ b/pandas/tests/io/json/test_pandas.py @@ -1296,25 +1296,6 @@ def test_datetime_tz(self): s_naive = Series(tz_naive) assert stz.to_json() == s_naive.to_json() - @pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") - @pytest.mark.filterwarnings("ignore:DataFrame.to_sparse:FutureWarning") - @pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") - def test_sparse(self): - # GH4377 df.to_json segfaults with non-ndarray blocks - df = pd.DataFrame(np.random.randn(10, 4)) - df.loc[:8] = np.nan - - sdf = df.to_sparse() - expected = df.to_json() - assert expected == sdf.to_json() - - s = pd.Series(np.random.randn(10)) - s.loc[:8] = np.nan - ss = s.to_sparse() - - expected = s.to_json() - assert expected == ss.to_json() - def test_tz_is_utc(self): from pandas.io.json import dumps From 238db691e7813a8c250b7f5a4351f5930f7e6eb4 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 12 Sep 2019 22:04:18 -0500 Subject: [PATCH 04/33] round 4 --- doc/source/whatsnew/v1.0.0.rst | 2 + pandas/tests/frame/test_indexing.py | 7 --- .../tests/io/generate_legacy_storage_files.py | 48 --------------- pandas/tests/io/test_packers.py | 43 ------------- pandas/tests/reshape/test_reshape.py | 10 ++-- pandas/tests/series/test_api.py | 6 -- pandas/tests/series/test_combine_concat.py | 35 ----------- pandas/tests/series/test_missing.py | 60 ------------------- 8 files changed, 7 insertions(+), 204 deletions(-) diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index 9998a9a847643..faf3abfb63e33 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -78,6 +78,8 @@ Deprecations Removal of prior version deprecations/changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Removed ``SparseSeries`` and ``SparseDataFrame`` (:issue:``) - Removed the previously deprecated :meth:`Series.get_value`, :meth:`Series.set_value`, :meth:`DataFrame.get_value`, :meth:`DataFrame.set_value` (:issue:`17739`) - Changed the the default value of `inplace` in :meth:`DataFrame.set_index` and :meth:`Series.set_axis`. It now defaults to False (:issue:`27600`) - :meth:`pandas.Series.str.cat` now defaults to aligning ``others``, using ``join='left'`` (:issue:`27611`) diff --git a/pandas/tests/frame/test_indexing.py b/pandas/tests/frame/test_indexing.py index a78b2ab7d1c4c..96f56bdef6286 100644 --- a/pandas/tests/frame/test_indexing.py +++ b/pandas/tests/frame/test_indexing.py @@ -2145,13 +2145,6 @@ def test_loc_duplicates(self): df.loc[trange[bool_idx], "A"] += 6 tm.assert_frame_equal(df, expected) - @pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") - def test_iloc_sparse_propegate_fill_value(self): - from pandas.core.sparse.api import SparseDataFrame - - df = SparseDataFrame({"A": [999, 1]}, default_fill_value=999) - assert len(df["A"].sp_values) == len(df.iloc[:, 0].sp_values) - def test_iat(self, float_frame): for i, row in enumerate(float_frame.index): diff --git a/pandas/tests/io/generate_legacy_storage_files.py b/pandas/tests/io/generate_legacy_storage_files.py index 2d2938697bd80..213d1d9446e1b 100755 --- a/pandas/tests/io/generate_legacy_storage_files.py +++ b/pandas/tests/io/generate_legacy_storage_files.py @@ -53,10 +53,7 @@ Period, RangeIndex, Series, - SparseDataFrame, - SparseSeries, Timestamp, - bdate_range, date_range, period_range, timedelta_range, @@ -89,47 +86,6 @@ _loose_version = LooseVersion(pandas.__version__) -def _create_sp_series(): - nan = np.nan - - # nan-based - arr = np.arange(15, dtype=np.float64) - arr[7:12] = nan - arr[-1:] = nan - - bseries = SparseSeries(arr, kind="block") - bseries.name = "bseries" - return bseries - - -def _create_sp_tsseries(): - nan = np.nan - - # nan-based - arr = np.arange(15, dtype=np.float64) - arr[7:12] = nan - arr[-1:] = nan - - date_index = bdate_range("1/1/2011", periods=len(arr)) - bseries = SparseSeries(arr, index=date_index, kind="block") - bseries.name = "btsseries" - return bseries - - -def _create_sp_frame(): - nan = np.nan - - data = { - "A": [nan, nan, nan, 0, 1, 2, 3, 4, 5, 6], - "B": [0, 1, 2, nan, nan, nan, 3, 4, 5, 6], - "C": np.arange(10).astype(np.int64), - "D": [0, 1, 2, 3, 4, 5, nan, nan, nan, nan], - } - - dates = bdate_range("1/1/2011", periods=10) - return SparseDataFrame(data, index=dates) - - def create_data(): """ create the pickle/msgpack data """ @@ -287,8 +243,6 @@ def create_data(): index=index, scalars=scalars, mi=mi, - sp_series=dict(float=_create_sp_series(), ts=_create_sp_tsseries()), - sp_frame=dict(float=_create_sp_frame()), cat=cat, timestamp=timestamp, offsets=off, @@ -308,8 +262,6 @@ def _u(x): def create_msgpack_data(): data = create_data() # Not supported - del data["sp_series"] - del data["sp_frame"] del data["series"]["cat"] del data["series"]["period"] del data["frame"]["cat_onecol"] diff --git a/pandas/tests/io/test_packers.py b/pandas/tests/io/test_packers.py index 33a11087f622d..95887bb69cdbc 100644 --- a/pandas/tests/io/test_packers.py +++ b/pandas/tests/io/test_packers.py @@ -585,49 +585,6 @@ def test_dataframe_duplicate_column_names(self): assert_frame_equal(result_3, expected_3) -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:DataFrame.to_sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:.*msgpack:FutureWarning") -class TestSparse(TestPackers): - def _check_roundtrip(self, obj, comparator, **kwargs): - - # currently these are not implemetned - # i_rec = self.encode_decode(obj) - # comparator(obj, i_rec, **kwargs) - msg = r"msgpack sparse (series|frame) is not implemented" - with pytest.raises(NotImplementedError, match=msg): - self.encode_decode(obj) - - def test_sparse_series(self): - - s = tm.makeStringSeries() - s[3:5] = np.nan - ss = s.to_sparse() - self._check_roundtrip(ss, tm.assert_series_equal, check_series_type=True) - - ss2 = s.to_sparse(kind="integer") - self._check_roundtrip(ss2, tm.assert_series_equal, check_series_type=True) - - ss3 = s.to_sparse(fill_value=0) - self._check_roundtrip(ss3, tm.assert_series_equal, check_series_type=True) - - def test_sparse_frame(self): - - s = tm.makeDataFrame() - s.loc[3:5, 1:3] = np.nan - s.loc[8:10, -2] = np.nan - ss = s.to_sparse() - - self._check_roundtrip(ss, tm.assert_frame_equal, check_frame_type=True) - - ss2 = s.to_sparse(kind="integer") - self._check_roundtrip(ss2, tm.assert_frame_equal, check_frame_type=True) - - ss3 = s.to_sparse(fill_value=0) - self._check_roundtrip(ss3, tm.assert_frame_equal, check_frame_type=True) - - @pytest.mark.filterwarnings("ignore:.*msgpack:FutureWarning") class TestCompression(TestPackers): """See https://github.com/pandas-dev/pandas/pull/9783 diff --git a/pandas/tests/reshape/test_reshape.py b/pandas/tests/reshape/test_reshape.py index 149930059d868..652c622a40921 100644 --- a/pandas/tests/reshape/test_reshape.py +++ b/pandas/tests/reshape/test_reshape.py @@ -273,7 +273,7 @@ def test_dataframe_dummies_subset(self, df, sparse): expected[["C"]] = df[["C"]] if sparse: cols = ["from_A_a", "from_A_b"] - expected[cols] = expected[cols].apply(lambda x: pd.SparseSeries(x)) + expected[cols] = expected[cols].astype(pd.SparseDtype("uint8", 0)) assert_frame_equal(result, expected) def test_dataframe_dummies_prefix_sep(self, df, sparse): @@ -292,7 +292,7 @@ def test_dataframe_dummies_prefix_sep(self, df, sparse): expected = expected[["C", "A..a", "A..b", "B..b", "B..c"]] if sparse: cols = ["A..a", "A..b", "B..b", "B..c"] - expected[cols] = expected[cols].apply(lambda x: pd.SparseSeries(x)) + expected[cols] = expected[cols].astype(pd.SparseDtype("uint8", 0)) assert_frame_equal(result, expected) @@ -329,7 +329,7 @@ def test_dataframe_dummies_prefix_dict(self, sparse): columns = ["from_A_a", "from_A_b", "from_B_b", "from_B_c"] expected[columns] = expected[columns].astype(np.uint8) if sparse: - expected[columns] = expected[columns].apply(lambda x: pd.SparseSeries(x)) + expected[columns] = expected[columns].astype(pd.SparseDtype("uint8", 0)) assert_frame_equal(result, expected) @@ -495,7 +495,7 @@ def test_dataframe_dummies_drop_first_with_categorical(self, df, sparse, dtype): expected = expected[["C", "A_b", "B_c", "cat_y"]] if sparse: for col in cols: - expected[col] = pd.SparseSeries(expected[col]) + expected[col] = pd.SparseArray(expected[col]) assert_frame_equal(result, expected) def test_dataframe_dummies_drop_first_with_na(self, df, sparse): @@ -517,7 +517,7 @@ def test_dataframe_dummies_drop_first_with_na(self, df, sparse): expected = expected.sort_index(axis=1) if sparse: for col in cols: - expected[col] = pd.SparseSeries(expected[col]) + expected[col] = pd.SparseArray(expected[col]) assert_frame_equal(result, expected) diff --git a/pandas/tests/series/test_api.py b/pandas/tests/series/test_api.py index d204d7d2a1d7c..935d71db9b669 100644 --- a/pandas/tests/series/test_api.py +++ b/pandas/tests/series/test_api.py @@ -131,12 +131,6 @@ def test_sort_index_name(self): result = self.ts.sort_index(ascending=False) assert result.name == self.ts.name - @pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") - @pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") - def test_to_sparse_pass_name(self): - result = self.ts.to_sparse() - assert result.name == self.ts.name - def test_constructor_dict(self): d = {"a": 0.0, "b": 1.0, "c": 2.0} result = self.series_klass(d) diff --git a/pandas/tests/series/test_combine_concat.py b/pandas/tests/series/test_combine_concat.py index bf527bae297d9..f653f42eece7a 100644 --- a/pandas/tests/series/test_combine_concat.py +++ b/pandas/tests/series/test_combine_concat.py @@ -218,8 +218,6 @@ def test_combine_first_dt_tz_values(self, tz_naive_fixture): exp = pd.Series(exp_vals, name="ser1") assert_series_equal(exp, result) - @pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") - @pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") def test_concat_empty_series_dtypes(self): # booleans @@ -273,39 +271,6 @@ def test_concat_empty_series_dtypes(self): == "object" ) - # sparse - # TODO: move? - result = pd.concat( - [Series(dtype="float64").to_sparse(), Series(dtype="float64").to_sparse()] - ) - assert result.dtype == "Sparse[float64]" - - # GH 26705 - Assert .ftype is deprecated - with tm.assert_produces_warning(FutureWarning): - assert result.ftype == "float64:sparse" - - result = pd.concat( - [Series(dtype="float64").to_sparse(), Series(dtype="float64")] - ) - # TODO: release-note: concat sparse dtype - expected = pd.core.sparse.api.SparseDtype(np.float64) - assert result.dtype == expected - - # GH 26705 - Assert .ftype is deprecated - with tm.assert_produces_warning(FutureWarning): - assert result.ftype == "float64:sparse" - - result = pd.concat( - [Series(dtype="float64").to_sparse(), Series(dtype="object")] - ) - # TODO: release-note: concat sparse dtype - expected = pd.core.sparse.api.SparseDtype("object") - assert result.dtype == expected - - # GH 26705 - Assert .ftype is deprecated - with tm.assert_produces_warning(FutureWarning): - assert result.ftype == "object:sparse" - def test_combine_first_dt64(self): from pandas.core.tools.datetimes import to_datetime diff --git a/pandas/tests/series/test_missing.py b/pandas/tests/series/test_missing.py index ddd2c566f4cda..f459ae9e7845d 100644 --- a/pandas/tests/series/test_missing.py +++ b/pandas/tests/series/test_missing.py @@ -6,7 +6,6 @@ import pytz from pandas._libs.tslib import iNaT -from pandas.errors import PerformanceWarning import pandas.util._test_decorators as td import pandas as pd @@ -992,65 +991,6 @@ def test_series_fillna_limit(self): expected[:3] = np.nan assert_series_equal(result, expected) - @pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") - @pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") - def test_sparse_series_fillna_limit(self): - index = np.arange(10) - s = Series(np.random.randn(10), index=index) - - ss = s[:2].reindex(index).to_sparse() - # TODO: what is this test doing? why are result an expected - # the same call to fillna? - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - # TODO: release-note fillna performance warning - result = ss.fillna(method="pad", limit=5) - expected = ss.fillna(method="pad", limit=5) - expected = expected.to_dense() - expected[-3:] = np.nan - expected = expected.to_sparse() - assert_series_equal(result, expected) - - ss = s[-2:].reindex(index).to_sparse() - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - result = ss.fillna(method="backfill", limit=5) - expected = ss.fillna(method="backfill") - expected = expected.to_dense() - expected[:3] = np.nan - expected = expected.to_sparse() - assert_series_equal(result, expected) - - @pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") - @pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") - def test_sparse_series_pad_backfill_limit(self): - index = np.arange(10) - s = Series(np.random.randn(10), index=index) - s = s.to_sparse() - - result = s[:2].reindex(index, method="pad", limit=5) - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - expected = s[:2].reindex(index).fillna(method="pad") - expected = expected.to_dense() - expected[-3:] = np.nan - expected = expected.to_sparse() - assert_series_equal(result, expected) - - result = s[-2:].reindex(index, method="backfill", limit=5) - with tm.assert_produces_warning( - PerformanceWarning, raise_on_extra_warnings=False - ): - expected = s[-2:].reindex(index).fillna(method="backfill") - expected = expected.to_dense() - expected[:3] = np.nan - expected = expected.to_sparse() - assert_series_equal(result, expected) - - @pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") def test_series_pad_backfill_limit(self): index = np.arange(10) s = Series(np.random.randn(10), index=index) From 744879513a77905430a39481499efe5908755efa Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 06:47:55 -0500 Subject: [PATCH 05/33] remove hdf --- pandas/tests/io/pytables/test_pytables.py | 32 ----------------------- 1 file changed, 32 deletions(-) diff --git a/pandas/tests/io/pytables/test_pytables.py b/pandas/tests/io/pytables/test_pytables.py index 77cac00882771..6e57371b730ba 100644 --- a/pandas/tests/io/pytables/test_pytables.py +++ b/pandas/tests/io/pytables/test_pytables.py @@ -2353,38 +2353,6 @@ def test_series(self): ts3 = Series(ts.values, Index(np.asarray(ts.index, dtype=object), dtype=object)) self._check_roundtrip(ts3, tm.assert_series_equal, check_index_type=False) - @ignore_sparse - @ignore_series_tosparse - def test_sparse_series(self): - - s = tm.makeStringSeries() - s.iloc[3:5] = np.nan - ss = s.to_sparse() - self._check_roundtrip(ss, tm.assert_series_equal, check_series_type=True) - - ss2 = s.to_sparse(kind="integer") - self._check_roundtrip(ss2, tm.assert_series_equal, check_series_type=True) - - ss3 = s.to_sparse(fill_value=0) - self._check_roundtrip(ss3, tm.assert_series_equal, check_series_type=True) - - @ignore_sparse - @ignore_dataframe_tosparse - def test_sparse_frame(self): - - s = tm.makeDataFrame() - s.iloc[3:5, 1:3] = np.nan - s.iloc[8:10, -2] = np.nan - ss = s.to_sparse() - - self._check_double_roundtrip(ss, tm.assert_frame_equal, check_frame_type=True) - - ss2 = s.to_sparse(kind="integer") - self._check_double_roundtrip(ss2, tm.assert_frame_equal, check_frame_type=True) - - ss3 = s.to_sparse(fill_value=0) - self._check_double_roundtrip(ss3, tm.assert_frame_equal, check_frame_type=True) - def test_float_index(self): # GH #454 From f285272d893fd03dc8487400ce23488836b89c49 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 06:52:17 -0500 Subject: [PATCH 06/33] some more --- pandas/core/arrays/sparse.py | 8 ++-- .../tests/arrays/sparse/test_arithmetics.py | 2 - pandas/tests/io/pytables/test_pytables.py | 47 ------------------- 3 files changed, 4 insertions(+), 53 deletions(-) diff --git a/pandas/core/arrays/sparse.py b/pandas/core/arrays/sparse.py index 8c7339aea0a45..c612e136220dd 100644 --- a/pandas/core/arrays/sparse.py +++ b/pandas/core/arrays/sparse.py @@ -2046,10 +2046,10 @@ def to_coo(self, row_levels=(0,), column_levels=(1,), sort_labels=False): (2, 1, 'b', 0), (2, 1, 'b', 1)], names=['A', 'B', 'C', 'D']) - >>> ss = s.to_sparse() - >>> A, rows, columns = ss.to_coo(row_levels=['A', 'B'], - column_levels=['C', 'D'], - sort_labels=True) + >>> ss = s.astype("Sparse") + >>> A, rows, columns = ss.sparse.to_coo(row_levels=['A', 'B'], + ... column_levels=['C', 'D'], + ... sort_labels=True) >>> A <3x4 sparse matrix of type '' with 3 stored elements in COOrdinate format> diff --git a/pandas/tests/arrays/sparse/test_arithmetics.py b/pandas/tests/arrays/sparse/test_arithmetics.py index 9d5f0e365eefd..071a8db707b69 100644 --- a/pandas/tests/arrays/sparse/test_arithmetics.py +++ b/pandas/tests/arrays/sparse/test_arithmetics.py @@ -21,8 +21,6 @@ def mix(request): return request.param -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -@pytest.mark.filterwarnings("ignore:Series.to_sparse:FutureWarning") class TestSparseArrayArithmetics: _base = np.array diff --git a/pandas/tests/io/pytables/test_pytables.py b/pandas/tests/io/pytables/test_pytables.py index 6e57371b730ba..856d97e29f2c0 100644 --- a/pandas/tests/io/pytables/test_pytables.py +++ b/pandas/tests/io/pytables/test_pytables.py @@ -71,13 +71,6 @@ ignore_natural_naming_warning = pytest.mark.filterwarnings( "ignore:object name:tables.exceptions.NaturalNameWarning" ) -ignore_sparse = pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") -ignore_dataframe_tosparse = pytest.mark.filterwarnings( - "ignore:DataFrame.to_sparse:FutureWarning" -) -ignore_series_tosparse = pytest.mark.filterwarnings( - "ignore:Series.to_sparse:FutureWarning" -) # contextmanager to ensure the file cleanup @@ -2677,40 +2670,6 @@ def test_overwrite_node(self): tm.assert_series_equal(store["a"], ts) - @ignore_sparse - @ignore_dataframe_tosparse - def test_sparse_with_compression(self): - - # GH 2931 - - # make sparse dataframe - arr = np.random.binomial(n=1, p=0.01, size=(1000, 10)) - df = DataFrame(arr).to_sparse(fill_value=0) - - # case 1: store uncompressed - self._check_double_roundtrip( - df, tm.assert_frame_equal, compression=False, check_frame_type=True - ) - - # case 2: store compressed (works) - self._check_double_roundtrip( - df, tm.assert_frame_equal, compression="zlib", check_frame_type=True - ) - - # set one series to be completely sparse - df[0] = np.zeros(1000) - - # case 3: store df with completely sparse series uncompressed - self._check_double_roundtrip( - df, tm.assert_frame_equal, compression=False, check_frame_type=True - ) - - # case 4: try storing df with completely sparse series compressed - # (fails) - self._check_double_roundtrip( - df, tm.assert_frame_equal, compression="zlib", check_frame_type=True - ) - def test_select(self): with ensure_clean_store(self.path) as store: @@ -3858,8 +3817,6 @@ def test_start_stop_multiple(self): expected = df.loc[[0], ["foo", "bar"]] tm.assert_frame_equal(result, expected) - @ignore_sparse - @ignore_dataframe_tosparse def test_start_stop_fixed(self): with ensure_clean_store(self.path) as store: @@ -3899,10 +3856,6 @@ def test_start_stop_fixed(self): df = tm.makeDataFrame() df.iloc[3:5, 1:3] = np.nan df.iloc[8:10, -2] = np.nan - dfs = df.to_sparse() - store.put("dfs", dfs) - with pytest.raises(NotImplementedError): - store.select("dfs", start=0, stop=5) def test_select_filter_corner(self): From b6fb1aabc19515672e840c7d0b30fafee1b88844 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 06:53:51 -0500 Subject: [PATCH 07/33] cleanup --- pandas/tests/sparse/common.py | 0 pandas/tests/sparse/test_format.py | 0 pandas/tests/sparse/test_groupby.py | 0 pandas/tests/sparse/test_indexing.py | 0 pandas/tests/sparse/test_pivot.py | 0 pandas/tests/sparse/test_reshape.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 pandas/tests/sparse/common.py delete mode 100644 pandas/tests/sparse/test_format.py delete mode 100644 pandas/tests/sparse/test_groupby.py delete mode 100644 pandas/tests/sparse/test_indexing.py delete mode 100644 pandas/tests/sparse/test_pivot.py delete mode 100644 pandas/tests/sparse/test_reshape.py diff --git a/pandas/tests/sparse/common.py b/pandas/tests/sparse/common.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/pandas/tests/sparse/test_format.py b/pandas/tests/sparse/test_format.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/pandas/tests/sparse/test_groupby.py b/pandas/tests/sparse/test_groupby.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/pandas/tests/sparse/test_indexing.py b/pandas/tests/sparse/test_indexing.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/pandas/tests/sparse/test_pivot.py b/pandas/tests/sparse/test_pivot.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/pandas/tests/sparse/test_reshape.py b/pandas/tests/sparse/test_reshape.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 From c476b21e4dac30e55d1087a3a24f950d245b9179 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 06:56:19 -0500 Subject: [PATCH 08/33] note --- doc/source/whatsnew/v1.0.0.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index faf3abfb63e33..292677b5b90ba 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -76,10 +76,17 @@ Deprecations .. _whatsnew_1000.prior_deprecations: + +Removed SparseSeries and SparseDataFrame +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``SparseSeries`` and ``SparseDataFrame`` have been removed (:issue:`28425`). +We recommend using a ``Series`` or ``DataFrame`` with sparse values instead. +See :ref:`sparse.migration` for help with migrating existing code. + Removal of prior version deprecations/changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Removed ``SparseSeries`` and ``SparseDataFrame`` (:issue:``) - Removed the previously deprecated :meth:`Series.get_value`, :meth:`Series.set_value`, :meth:`DataFrame.get_value`, :meth:`DataFrame.set_value` (:issue:`17739`) - Changed the the default value of `inplace` in :meth:`DataFrame.set_index` and :meth:`Series.set_axis`. It now defaults to False (:issue:`27600`) - :meth:`pandas.Series.str.cat` now defaults to aligning ``others``, using ``join='left'`` (:issue:`27611`) From fc34fe8bf1b155b05733e14c690eac05690057d9 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 10:56:19 -0500 Subject: [PATCH 09/33] fixups --- pandas/core/ops/__init__.py | 70 ----------------------------------- pandas/core/reshape/concat.py | 4 +- 2 files changed, 3 insertions(+), 71 deletions(-) diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index 3e56cb351273a..d5c0e1e9424fc 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -1106,76 +1106,6 @@ def f(self, other): # Sparse -def _cast_sparse_series_op(left, right, opname): - """ - For SparseSeries operation, coerce to float64 if the result is expected - to have NaN or inf values - - Parameters - ---------- - left : SparseArray - right : SparseArray - opname : str - - Returns - ------- - left : SparseArray - right : SparseArray - """ - from pandas.core.sparse.api import SparseDtype - - opname = opname.strip("_") - - # TODO: This should be moved to the array? - if is_integer_dtype(left) and is_integer_dtype(right): - # series coerces to float64 if result should have NaN/inf - if opname in ("floordiv", "mod") and (right.to_dense() == 0).any(): - left = left.astype(SparseDtype(np.float64, left.fill_value)) - right = right.astype(SparseDtype(np.float64, right.fill_value)) - elif opname in ("rfloordiv", "rmod") and (left.to_dense() == 0).any(): - left = left.astype(SparseDtype(np.float64, left.fill_value)) - right = right.astype(SparseDtype(np.float64, right.fill_value)) - - return left, right - - -def _arith_method_SPARSE_SERIES(cls, op, special): - """ - Wrapper function for Series arithmetic operations, to avoid - code duplication. - """ - op_name = _get_op_name(op, special) - - def wrapper(self, other): - if isinstance(other, ABCDataFrame): - return NotImplemented - elif isinstance(other, ABCSeries): - return _sparse_series_op(self, other, op, op_name) - elif is_scalar(other): - with np.errstate(all="ignore"): - new_values = op(self.values, other) - return self._constructor(new_values, index=self.index, name=self.name) - else: # pragma: no cover - raise TypeError( - "operation with {other} not supported".format(other=type(other)) - ) - - wrapper.__name__ = op_name - return wrapper - - -def _sparse_series_op(left, right, op, name): - left, right = left.align(right, join="outer", copy=False) - new_index = left.index - new_name = get_op_result_name(left, right) - - from pandas.core.arrays.sparse import _sparse_array_op - - lvalues, rvalues = _cast_sparse_series_op(left.values, right.values, name) - result = _sparse_array_op(lvalues, rvalues, op, name) - return left._constructor(result, index=new_index, name=new_name) - - def maybe_dispatch_ufunc_to_dunder_op( self: ArrayLike, ufunc: Callable, method: str, *inputs: ArrayLike, **kwargs: Any ): diff --git a/pandas/core/reshape/concat.py b/pandas/core/reshape/concat.py index 1b3502f1c8346..60bab817705e3 100644 --- a/pandas/core/reshape/concat.py +++ b/pandas/core/reshape/concat.py @@ -713,6 +713,8 @@ def _get_series_result_type(result, objs=None): return appropriate class of Series concat input is either dict or array-like """ + # TODO: See if we can just inline with _constructor_expanddim + # now that sparse is removed. from pandas import DataFrame # concat Series with axis 1 @@ -727,5 +729,5 @@ def _get_frame_result_type(result, objs): """ return appropriate class of DataFrame-like concat """ - # TODO: just inline this + # TODO: just inline this as _constructor. return objs[0] From 3cc47659dc16f57370bc8e4a82463ec69fae7dc0 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 10:57:25 -0500 Subject: [PATCH 10/33] pickle changes --- ...ckle => 0.20.3_x86_64_darwin_3.5.6.pickle} | Bin 127923 -> 127523 bytes .../tests/io/generate_legacy_storage_files.py | 56 +++++++++++++++++- pandas/tests/io/test_pickle.py | 4 +- 3 files changed, 56 insertions(+), 4 deletions(-) rename pandas/tests/io/data/legacy_pickle/0.20.3/{0.20.3_x86_64_darwin_3.5.2.pickle => 0.20.3_x86_64_darwin_3.5.6.pickle} (85%) diff --git a/pandas/tests/io/data/legacy_pickle/0.20.3/0.20.3_x86_64_darwin_3.5.2.pickle b/pandas/tests/io/data/legacy_pickle/0.20.3/0.20.3_x86_64_darwin_3.5.6.pickle similarity index 85% rename from pandas/tests/io/data/legacy_pickle/0.20.3/0.20.3_x86_64_darwin_3.5.2.pickle rename to pandas/tests/io/data/legacy_pickle/0.20.3/0.20.3_x86_64_darwin_3.5.6.pickle index 9777319465de6a012f3d071e436bc07600ee66ae..05cefcfd7974b5abdd67220647ecb2abec317fea 100644 GIT binary patch delta 13558 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 delta 13906 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�ng-uP@Um-3yy` zXtKt?1RAiy_26U)XF_E?yU+K)#j(UAzg#-clVXUB=eGS94*CYjkMKXm(E8#`1`nB9 zaW+47yr3c~N{cga=j&6g{r=B>^VMH2O{25Vm*9Q4e&EaBc;gKmiV2JzK7y>!A(J*D zC?ktvJ8JD){_l^z2;$mSl~M8)TNgSw>Kt+sCusGs{8tH^Q){sL`C1$`rhmU7iG<48 z;xVMPx@|rBMMhPe2V!A;5LtNR%sV!%_B^AVqEr2}FpVGcyF@YzY|ZaDo|ui@1F{a0 zd}Dj)-)nj}9xkSK8AqRQ?r&dJw!v2{ACVl8wZgC+&s0kWP)oV@i0GsTYbXbjkOVaY zckl1%thyKmneAHBe>WI=SLJWF2Bu?_W Date: Fri, 13 Sep 2019 11:57:22 -0500 Subject: [PATCH 11/33] pickle compat --- pandas/compat/pickle_compat.py | 29 ++++++++++++++++-- .../0.20.3/0.20.3_x86_64_darwin_3.5.2.pickle | Bin 0 -> 127923 bytes 2 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 pandas/tests/io/data/legacy_pickle/0.20.3/0.20.3_x86_64_darwin_3.5.2.pickle diff --git a/pandas/compat/pickle_compat.py b/pandas/compat/pickle_compat.py index 87240a9f986c3..d08f2178bb1a2 100644 --- a/pandas/compat/pickle_compat.py +++ b/pandas/compat/pickle_compat.py @@ -5,6 +5,7 @@ import copy import pickle as pkl import sys +from typing import Any from pandas import Index @@ -54,6 +55,20 @@ def load_reduce(self): raise +class _LoadSparseSeries: + def __new__(cls) -> Any: + from pandas import Series + + return Series() + + +class _LoadSparseFrame: + def __new__(cls) -> Any: + from pandas import DataFrame + + return DataFrame() + + # If classes are moved, provide compat here. _class_locations_map = { ("pandas.core.sparse.array", "SparseArray"): ("pandas.core.arrays", "SparseArray"), @@ -101,12 +116,12 @@ def load_reduce(self): "SparseArray", ), ("pandas.sparse.series", "SparseSeries"): ( - "pandas.core.sparse.series", - "SparseSeries", + "pandas.compat.pickle_compat", + "_LoadSparseSeries", ), ("pandas.sparse.frame", "SparseDataFrame"): ( "pandas.core.sparse.frame", - "SparseDataFrame", + "_LoadSparseFrame", ), ("pandas.indexes.base", "_new_Index"): ("pandas.core.indexes.base", "_new_Index"), ("pandas.indexes.base", "Index"): ("pandas.core.indexes.base", "Index"), @@ -139,6 +154,14 @@ def load_reduce(self): "pandas.core.indexes.numeric", "Float64Index", ), + ("pandas.core.sparse.series", "SparseSeries"): ( + "pandas.compat.pickle_compat", + "_LoadSparseSeries", + ), + ("pandas.core.sparse.frame", "SparseDataFrame"): ( + "pandas.compat.pickle_compat", + "_LoadSparseFrame", + ), } diff --git a/pandas/tests/io/data/legacy_pickle/0.20.3/0.20.3_x86_64_darwin_3.5.2.pickle b/pandas/tests/io/data/legacy_pickle/0.20.3/0.20.3_x86_64_darwin_3.5.2.pickle new file mode 100644 index 0000000000000000000000000000000000000000..9777319465de6a012f3d071e436bc07600ee66ae GIT binary patch literal 127923 zcmb^42bfgFx+v@w$T{bnbIv&^l`x=^R1_5xD2gIcQ52CN5=0Rt znRCQ^d*F4Sz4zVs-2XiPt$FCTS9kSVRkdo>S9I4*&q}j=^YAc8O*2v|Bu68Mrp!o5 zPBSiPaIKW&0%Q7(8rW}qwE?5YB~=?bYGBfor191I_Zy#-lANi}sHDk#I;1|9vMeRJ z;D5?Fa^i>yLlarMr22Q()T5*u(XW5fi18`C68H0tnmBUI)XRss^n!ll#`T+;lANW_ zxTFE2M~$B_ZsLFmsfQ@^pAIo-+~{dZqf(Nyw!3tvYnzsrUcD@(Q!qEBb=W^;eoFV0 z3Z24E8Rn)Wr@eHjgaao`9g~#0&uD1PM9xm()cI6b9n*RCD^N~B9Soq`D|{TH;( z5CmZuMRA;x*c_65NUqeq56SaS`yNs>HCK4~CpA}6a>nta$4wYLZeY^5l&*;yCrwi0 zA?b#sNlyFE&5>qQzmbV1O*?4lxbYJbjX$v8gnlVQvj3yJ=@Td6pQ`m=2TqeHMWN)V zTZ4bD#E@eDT7~ARRY+~=xPQNKDXF*961Rg!k51f+W%VFC~?e2m-jb%RHD@Jgvq1-YadC8LMJM7>C_IXKBR5Zr9F>N z&MUFOl?E^O5bPv#Qv8a z&2sr5N|Kzm&!v`5&ik)DYeLevQT-CVGdWAE5u*nT@6vBnzrl%tAQj0NZ$drHF|KCnorpw)=%RhIMG|6F;fA8ywJ+zKSjZaBjE{UHBDf3c0 z@`Bb^2En0X`&#~(GYE=p>a{r4kuASFo$B@@^Q!)oF!R8|(^I{e|K#AG6Sm(la!aZ! z8bxU@CcLnJMUPbL^s2n>V$RgvCPx#dC9aD!gT^I|P3d~M|1In~WKnWHjWc0<(zv0C z;b!!pLF1DqBw8hNtBK=>j!GIozD;6y_(yk0Gko&E@u{6E-Q|oa$r=7`$wV(mZP0;9 zBNCTW>WGl~lX}f2hocg8OwKrJ^th4zMhu;nl(JyrbHOQn@*Yj_lr2ZT+pF){$=3!j|EK=CrXrh#{TnOwk&9!CcPY~Z96aV zlbUtizm6KIEwf=k>;EUU9vCJB-c3Epsp(P!C(L+h>r&zW(I5Z+Ry9*%pQ(-UkE&j}9{&zY|8clYZO7E% zcF4#?MESQk_TRr?)c^DaWB#WvNDXqvmDB;MMzxxCs@1#{#M1RojVza}b|yYVSQ)=x~la9G{MSsd0N zag)dpUCuJBabk1G-hX>K?v&=ApFZ_(Pe+~Ne}4MAiAl+6`XwfNDMJQLOiW4oCujXf z)Ez!FF)>I^d#M5`6Z?-(jt3;FnOcjqLq|=hSuZ6y|38Pf#FI&bM~@pipx=nZ5Z?Nq zw*E1MXYTWNrqlzbOD_D+uTLE9(yTKvbxXv90g1t_!>9@M>ij*pC5}6!+J8FFT|S8u zJ>#bZty&bX>hr1nG&Nh8V zP1%h97F2_l9&g3gY{Rx}$M(E}9oUhb*qL3}mEHLFpnH|=?(D&72n^ zIFqwDn{#+8Z{u9f<9y!E1zgBQOy(WDlXr13mvAYUF@?*yf_L*C-piF-#noKHwY-n_ z^8v2ogM5hV`7j^hqujv9_&A^7lYELBxrtBn8E)pYe2!cAJYV2ezQ}Fd&K=yzUEIx= z_%iqK6~4;7e2uSjAK&19zR3eT$U{8Lw|Ina^C*w;I8X2$p5(iHkEi%PPxAwQ$TK|4 zb3D%r{D>d(6Mo9i_&LAem;8!f^BaE4@Ay4`;E()?Kl37g;jjFSzw;0N$-nq_;19#g z4@8WahH06O>6w8UnTeU1g;|-6*_nemnTxrZhk2Qg`B{JkS%`&Mghg45#aV(SS&F4u zhGkifSxr9r(j452s6}+4G@LsOuDz4@luH}8apAT>yALK(^ z&xiR4ALRx<#>e>tpX5{A$W45j&u}xJ<#XJ^=lKG+@ZuuH-7N<{GZ$eY~F!a2+4yLtM{?`3N8720q5e`2?TjQ{2c+ ze45X2GoR&i+`{Mi0=M!-ZsT_D;7;!1Zob5qxreXtRqo|$e4YFF2KVz#9^gS9;$gnU zBYc}jd5p(-g75Go-{pHe#rJudAMit-;aQ&Jd0ya0{FtBcQ+~$J`31k^SNxja@LPVz z@A(6NM_d`aEdFeCGM3%I+(=k0WFe5WDGqW%&voSk! zFeh^{H}fzr^D#dQupkSuFpID#i?KLMup~>dG|R9o%dtEwup%q5GOMsEtFbz3uqJD< zHtVo1>#;r?upt|=M#LAPjMqR@o7H8&3u;6aSNa43*5>V zxsBVogFCs4yZI7d<{rMnSGkw3@pbOw8{E$~d4LCbh==(WkMM0C z6yN7*e!vfThG%(>=Xrr2@nfd0$+)ycD>+@C0iy?|eoX)Gi_9*6-kw;DIJDOPbxl#L z!|7AwaTr8FoLFy?HnCnOePUfn#voJT6ZI@X)*xGuJ;)K{3~~jzgFHdrAYYI_C=e73 z3I&COB0AmCPCAnS8g8spPU|^6G35h7CI?f3sll}1=3sg-Be*4)8O#c12Xlg3gWH0+!MtF8 zaC@*ISQsn{l7l;fJA=D|#lezbX|OCv36=*dg1duzf_sCN!Kz?&uqId=+!x#*JP@o4 z9t<7|)&~yEM}QbMS2NT(Bj0K6oM68oU^63$_P4 zf}O#xV0Z9R@N%#xcqMo>*c-eSydLZe-U#*wZw3c~gTbNTaPU@eBzQYG8XOCb2PcAe zf|J3!!F$1};Qioq@Imlla3(k#oD0qe7lMz1kAqKwPlL~b&x0?5FN3dwuY+%bZ-eiG z?}HzLAA_HQpM#6RFTtQj!!lvnuv}O^tPoZV zD}|NADq+>IT39`-5!MWAg|)*vVcoD^SU+qKHVhkujl(8k)390CJZurR3|ob*!!}{t zuwB?bydvxnc1$d@=^ShPNI+VHyY`tXME#;|92Q`jr)9rg+P zhW*0+;ec>pm=q2Q2Zuw#q2aJ_csL>)8IB4^hhxIA;ka;oI3b)EP6{W7Q^Kj?wD9I| zdN?DzC7c<~3TKCN!dt`J!nxtRaDI4uxFB2@E((*wJHk7|yTZlcl5lCbEKCWPhbzLn z!+XMe!10i7;X!uid^Ow~z81b7?hD@t_lIwW z2f~Blq403{R(K?QJ3Ja53y+5Tb@O1b=_+fY^JR6=1&xaSnkHU|` zPr^^b&%)2cFTyXwufngxZ^CcG@51lHAHpBQpTeKRi{UTfuiujJ*pAajA})-qdHODs9sb*Y7jMy z8bytxCQ;LJW8|Iz^qME>YL0TXbb~Rn$G|5nUZ! z6I~l!7hNCS5ZxH{jBbj0MZKdwQQxRv)IS;!4UCeaLDAr7NHjDW77dR^L?fe7(dcMQ zG&UL+jgKZo6QfDd zb96EKCHgh`E&4tBBl%7H5xh#5v-_-;kZa#G%gkwk4wZQ<5F?yxJ+C&E*F=NE5sG!N^#}5N?bLr z7FUmJ#5LnuaqYNHTsN*4*N+>-4dX^}Jh%#W%;(;~DWS@yvKu zJUgBf-x}W*&yDBB^W)p&1@XdoQJfs#5#Jf#6)%pL#7pC4aZ0>AUJ>6N-xJ>(uZ&m4 ztK&8C+W5Zs{`i4-UHoADP`o~VIDRC4G~N(D7C#<85kDC}6>p3;#ZSl2#GB)1Nu@z(gocw4+Z-VyJNcg4Hom*SV>J@G5?tMT6WwfOaTU;IYAKYlYl5Fd;W#fRg! z;v@0f@zMBLd^|o8zZ0K~-;LjkPsQ)Yr{fRe592fO+4x+1KE4ot6n}i_S0j=$^ttp4 z4@m=4S5zmbNh}C#kTR@m;$8U5i}#0JmAL!gFP~3cC~WzBzr;fA{)vByMSo#p$zhoI zWrJ`~;@{x^T1r2xcj^xReJ;`|btj2m$VeZAQJf}iy42~*u%U^Zsf9{irC7Vxe<;5a8`pvHtFws|MoV+|9A~;Ci~0GEX>Mm%+4Il$z06MJj}~{%+CTW z$U-d4A}q>cEY1=v$xCvh^Ta4M(qW=`h}-olxj z#o3(0TX`GjavtaNb}ryTE@Cq8;GMjSi@AhLxr`}X&K10y_wZh>kJTYksy`2&CCPyCq|`3ryLZ~UEq@K655#9J4aQpkug z(=aX5F+DRdBQr5GvoI^OF*|cGCv!13^Dr;-F+U5iAPccDi?Aq*u{cYxBulY0%djlV zu{##2Cu|EHv!Zozrh>h8VP1%gi*@7+Eimlm(ZP||P zc?CPLBRjD(yD)VIotnDwN~TVyQ&V^LVCqaZHC@ANc^$9k4ZM*(c@ujvb(Wo)`miti zu|EfJAd@(VgE@q$bMn+QjKevCBRPtrIfi37j^jCj6FG^KIfYX>jW=^TXYdxz)0@8kV^fa~}m zAL4pG%t!brH}Ek&&L{XJpW;St;?sPFoB1rC;}$;87r2!#avQgE2X}H8ck?B_%sqUC zuW~P6Y}ipYk()&M){Szv9>YhTrl#e$OBHBY)!0yvSepD}Uqf{DXh;FD5>?xRgRhjG2aM znU3k1ff<>JnVE%InT^?*gE^UtxtWJ~nUDEdfCX8Ig;|6}S&YS5f+bmsrCEk$S&rpd zffZSam05*VS&h|MgEd);wONOCS&#MEfDPG*joE}v*^JHEf-TvKt=Wcc*^cdb1v{`K zJFzpnuq(UqN?yh8?7^#f4X@>Oyq-7kM)u@Q?8V;f!@lgt{v5!8OyVF8<`53$Fb?Mk zj^rqg<`|CUIF9E8PUIv`<`holG~UeVoWWZ-le0LRb9gIn<6O?;eBRClT*yUC<{i9~ zcX2V7a4DBDh0D2uck>?J%avTk)m+21ypQ+u0j}eNe2DA$FdyNg+`z~9IG^B?e2N>n ziBIzxZsxOmj$8OVU*J~0$Zg!t9o)%X+|8HxGWYNmzRJCPjjwYb-{5|}$pbvdLp;p4 zc!Y2BD39?tPw*X{_e5-iD5EX^`3%W^Ew3arRVtjsE`%4)368m!4$tj#*C z%X+NO25iViY|JKX%4TfN7Hr8@Y|S=o%XVzfE7*Y@*@>Omgyo-yugiE=MDO}DKyqov%UasUS zuI3u9<$b)L4{#kHggm$`?p@Kx^RYkZyi_y+g$O&;Ju9^zrX#Up&1M|q6Ld4lioB;VzGJjM5U znji2(p5a-Z<9S}-NBo$d@Kb)q&-n$v8sP;1APWo(DvXnTBbZj_H|!8JUThnT1)IjoF!lIhl*OnTL6qkNH`E1zCuNS%gJd zjKx`kC0UB4S%zg9=w{@@LFES>v;ojWKZ72UhK_2 z?8|=a&jB3BBo5+W4&hJ^<8Y4PNRHxYj^S92<9JTsL{8#lPT^Ee1 z_%T1>r~Hhc^9z2-ulO~;;kW#b-}49l$e;K#FY*`u%HQ}q|KOkei+}%d%rLF^&zNbL zmg$(D8JLlon3-9amD!k`Ihd2Vn45W+m-(2V1z3=USeQjvl*L$_C0LTBSej*6mgQKU z64&!i+ z;7E?*XpZ4nj^lVv;6zU1WKQ8!PUFp-&KbOgGdYX1Ifu9MHqPZd&gbo1z=d4IWZuC$ zc^4OR372viQ@ETfcsK9iy^963@i`>TT+`*mP#oc^~FLMuH;j7%s*Z4a3@eS_hn>@gSJjBC% zi%0l2kMbCg^90}FNxsYXc#7}yG(X^nJj1g*$Md|vkN7b^;ivqJpYscT$*=e|zu~w1 zj^FbK{>Y#BGcWQN{>tC@JOALH{EL_Vz<46`hv~$B#!SPsOvm)hz>Lhq%*?{9%*O1@ z!JN#++|0wg%*XsJz=ABq!Ysm~EXLw2!ICV+(k#QWEXVS!z>2KI%B;ewtj6lB!J4ea z+N{I6tjGFnz=mwZ#%#i-Y{uqn!Io^r)@;MJY{&Mzf*shAo!FUO*p=OQC9h(4_TbgL zhS%~sUe6nNBYW~D_F`}LVPE!Re-7Y4CUFo4a|nlW7>9ENM{*QLa}39F9LIA4Cvp-e za|)+&8gJ%w&fqPa$yuDuIlPs(aW3a^K5yp&F61I6^A6t0ySSK3xRlG7!sT4SyLk`q zHSN_J| z`3L{xU;O*aFv9fWKVx3{MwG-;X>F%tdS+loW@2V$VOC~icIIGC=3;K&1-lqujBQ+fj6=z zZ(=X@W*_!tKlbMU4rCGsaWIE)D2H)4M{p!ZaWuzpEXQ#?CvYMsaWbcHDyQ*gPUj5X z!kL`K*_^{$c^l_)9_RCRF5p5gVlwaGoxF>Sxr9r(j452s6}+4G@LsOuDz4@luH}8a zpAT>yALK(^&xiR4ALRx<#>e>tpX5{A$W45j&u}xJ<#XJ^=lKG+@?WG&Wa9oA(%)@K7YWFt0a6E;{FdMGd;Y*5`4fNUMgGEH`5S-dAN-Sl@$WCc3^R)VjG2aMnU3k1ff<>J znVE%InT^?*gE^UtxtWJ~nUDEdfCX8Ig;|6}S&YS5f+bmsrCEk$S&rpdffZSam05*V zS&h|MgEd);wONOCS&#MEfDPG*joE}v*^JHEf-TvKt=Wcc*^cdb1v{`KJFzpnuq(Uq zN?yh8?7^#f4X@>Oyq-7kM)u@Q?8V;f!@lgt{v5!8OyVF8<`53$Fb?Mkj^rqg<`|CU zIF9E8PUIv`<`holG~UeVoWWZ-le0LRb9gIn<6O?;eBRClT*yUC<{i9~cX2V7a4DBD zh0D2uck>?J%avTk)m+21ypQ+u0j}eNe2DA$FdyNg+`z~9IG^B?e2N>niBIzxZsxOm zj$8OVU*J~0$Zg!t9o)%X+|8HxGWYNmzRJCPjjwYb-{5|}$pbvdLp;p4c!Y2BD39?t zPw*X{bQGcY4FF*CC;E3+{>b1)}!F*oxtFY_@! z3$P#yu`r9UD2uT;ORywMu{6uDEX%PxE3hIfu`;W$Dyy+NYp^D3u{P_lF6*&A8?Yf8 zu`!#lDVwo5Td*Ztu{GPUE!(j@uV4puWG8lJ7j|VgUdgN2ojrIpui>@4j@R=B-pHQ3 ziM`mHeb|@%*q;M9kVzcG!5qS&9LC`s!I2!r(Hz6E9LMpTz=@p1$(+KeoW`3uoilg~ zXL1&2a}ICiZJf(_oX^|2fD5^Z$-IMi@-8ms5-#O3rf@k|@NVA2d%2RUxSDIYmiO^~ zKEQQ+kPmS^ALb)`lpFXMALkQ%l236XH}Ppc!_9n_&v6T%=L_7*7rBkwxq~~oi@W&} zU*;aZ!dJPMukm&6;~U)1H+g^ud5DMk7LV|49_29}=Lx>UlYE!&@f6?ZX@0;Dd4^|s zj^}xSAMs;;!cX}bKj#&1-lqujBQ+fj6=zZ(=X@W*_!tKlbMU4rCGsaWIE) zD2H)4M{p!ZaWuzpEXQ#?CvYMsaWbcHDyQ*gPUj5X!kL`K*_^{$c^l_)9_RCRF5p5g zVlwaGoxF>Sxr9r(j452s6}+4G@LsOuDz4@luH}8apAT>yALK(^&xiR4ALRx<#>e>t zpX5{A$W45j&u}xJ<#XJ^=lKG+@$3qHvJo4z37fJRo3jO5vK3pi4coFE+w%%`U`KXhXLey%cH@=2irv|R zSMwTP%jfCHJtK^)8>9LixF&Ji5RQ5?-N9LsSW&k3B! zNu10noXTmunbSFgw{RwBaW?1hR^GI<=2mj<>OngD{<@nEtG1D+D z(=k0WFe5WDGqW%&voSk!Feh^{H}fzr^D#dQupkSuFpID#i?KLMup~>dG|R9o%dtEw zup%q5GOMsEtFbz3uqJD#;r?upt|=M#LAPjMqR z@o7H8&3u;6aSNa43*5>VxsBVogFCs4yZI7d<{rMnSGkw3@pbOw8{E$~d4LCbh==(W zkMM0C6yN7*e!vfThG%(>=Xrr2@ne3%Px%=?=NJ5vU-4^x!*BT= zzvmD9kw5WgUgR(QmA~a4+1Y{k}W!?tY4_Pl}}*pZ#snO)eG-FPLhVt4l7)x3t+@;YA6 z8+apo@+S6TZ}wqd_G5nz;6NsE5C?MzhjJK)a|B0n6i0Im$8sFUa{?!F5+`#Cr*ax^ z=5)^BEu6_&oXt7BmA7#&=W#x7=K?O|A|~?=-pRYTm`k{n%b3FDT*13}5AWqluHtI0 z;ac9u`}qLZ@j*Vs^?aC*@KJ8yV|<)X@JT+!joiei`3yJnSw6=te4a0GD_`U`Zs!i} z!OMIDo_zGX;UcScHxsPvfKi}j59^@e&=36|%w|SJuc$_Er4o~u3zQ%qg78X}p=!IfJ)wCTDRr=kQkE#<`rw`MjMA zxR8sO%sY4|@8V)E;ZiPR3YT*Q@8&(cmn*r7tGR}2c^~iR16;=k`4HFhVLrk~xq*-I zaX!H(`4l&D6QAZY+{|bB9Jla!zQC<~k=wYPJGhg(xSKEWW$xiCe3g6o8eiu=zQO%` zlLvT^hj^H8@d)4MQ6A%Qp5Qw?$#?l4Pw{=8<_G+cXLy$9c%B#d5kKZ9{FI;ZbAG`u z`4zwBH~g00@q7NjANdo1=0*O(U-=t<=O6r&e=*1@{xf3CG)&8MOwSC=$V|-4EX>Mm z%+4Il$z06MJj}~{%+CTW$U-d4A}q>cEY1=v$x zCvh^Ta4M(qW=`h}-olxj#o3(0TX`GjavtaNb}ryTE@Cq8;GMjSi@AhLxr`}X&K10y z_wZh>kJTYksy`2&CCPyCq|`3ryL zZ~UEq@K655AeZ>hh%wVJEz>bQGcY4FF*CC;E3+{>b1)}!F*oxtFY_@!3$P#yu`r9U zD2uT;ORywMu{6uDEX%PxE3hIfu`;W$Dyy+NYp^D3u{P_lF6*&A8?Yf8u`!#lDVwo5 zTd*Ztu{GPUE!(j@uV4puWG8lJ7j|VgUdgN2ojrIpui>@4j@R=B-pHQ3iM`mHeb|@% z*q;M9kVzcG!5qS&9LC`s!I2!r(Hz6E9LMpTz=@p1$(+KeoW`3uoilg~XL1&2a}ICi zZJf(_oX^|2fD5^Z$-IMi@-8ms5-#O3rf@k|@NVA2d%2RUxSDIYmiO^~KEQQ+kPmS^ zALb)`lpFXMALkQ%l236XH}Ppc!_9n_&v6T%=L_7*7rBkwxq~~oi@W&}U*;aZ!dJPM zukm&6;~U)1H+g^ud5DMk7LV|49_29}=Lx>UlYE!&@f6?ZX@0;Dd4^|sj^}xSAMs;; z!cX}bKj#I<=2mj<>4DyKoj2JTw(=r{?GXpa+6EialvoagAGY4}r7jrWY^D-av zvj7XS5DT*ii?SGtvjj`B6ic%V%d#BHvjQu!5-YO`tFjuavj%Ij7HhK(>#`o}vjH2j z5gW4!o3a_3vjtnS6v%nH;En9b zo7juJ*@u1EkNr7-1DV7@9Lymc%3&PN5gf@;9L+Ht%W)jf37p7DoXjbl%4xir(>a5; za3*JQHs|nG-p09{$N9XS3%HPrn9MtPC-357F5yxxV+xmZ1@GoPyq7DvimSPXYk42< z=L1~F2l)`!^I<;1N4bHI@o_%EC;1dNauc8CGu+H)`5d?KdA`7{e39F@ojbUbySSS# z@n!DeD}0rE`5Is6KEA>Ie3J)wkcW7fZ}AA<=20Hwah~8iJjr+Y9#8Rop5_PqkY{+7 z=XjnM_z^$mC;XJ3@pFE`FZmU}<~RJ7-|>6?z#sV&f96I0!e99tf9D_klYcSDEB-TL z%rs2PbWG0-%*ag4%q+~xY|PFa%*kBL%{ZuuH-7N<{GZ$eY~F!a2+4yLtM{?`3N8720q5e`2?Tj zQ{2c+e45X2GoR&i+`{Mi0=M!-ZsT_D;7;!1Zob5qxreXtRqo|$e4YFF2KVz#9^gS9 z;$gnUBYc}jd5p(-g75Go-{pHe#rJudAMit-;aQ&Jd0ya0{FtBcQ+~$J`31k^SNxja z@LPVz@A(6NFe|e$J9986 zb1^sbFfa2lKMSxR3$ZYZuqcbMI7_f3OR+S|uq?~5JS(swE3q=GuqvyuI%}{dYq2)# zurBMdJ{zzh8?iB)uqm6dIa{#h|6-}0q6Fyy1PYg{Qb><&+qTtX+vu@v^z@iLwr$(C zZQIuVyLYR>I0y1*>8;td2FXCf35* zSO@E3J*iI08rFC>)Jra4e3)@i+k|;v}4mkvIjX;xwF& zGjJx(!r3?n=i)q^j|*@iF2cpQ1efA6T#hSnC9cBNxCYnaI$Vz%a3gNQ&A0`(;x^ol zJ8&oN!rizB_u@X>j|cD|9>T+T1drk|JdP*uB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XE zH}EFj!rOQU@8UhYj}P!6KElWN1fSwFe2y>hCBDMf_y*tNJA98H@FRZ0&-ewu;y3(` zKkz61!r%A@|KdMXV$*-rP)7qzw9rNe1-j^=j{!;y!6+CNqhWN6fiW=_#>O}p7vo`k zOn?b75hlhYm=u#?a!i3KF%_o9G?*6CVS3Df88H)P#w?f>vtf43fjKc3=Egjj7xQ6$ zEPw^E5EjNFSQLw4aV&u)u@siZGFTSNVJMcz3Rn>Rk0dY#~N4@Yhi7ygLSbU z*2f0e5F24*Y=TX(88*ij*b-Y|7`DbX*cRJid+dN6u@iR2F4z^jVR!6-J+T+|#y;2= z`(b|^fCF(54#puk6o+9r4#x-_fg^Dgj>a)K7RTXuoPZN?5>CcQoPtwv8cxR3IVV;qc&@i0Cnz=W6x z6JrugipelJrofb#3R7bmOpEC-J!Zg+myhEV*_l6 zjj%B`!KT;@n_~-XiLEdUTVoq+i|w#IcEFC<2|HsK?26s6JNCey*b94OAMA_$us;sK zfj9^U;}9H*!!R6&V+4-CkvIxR;}{%^<8VAqz==2sCu1Z|!KpY6r{fHqiL-Dv&cV4j z59i|oT!@QsF)qQSxD1!$3S5b+a5b*MwYUz~;|AP_n{YF3!L7Irx8n}niMwz&?!mpd z5BK8%Jcx(zFdo69cnpu@2|S6X@HC#mvv>~A;|08km+&%P!K-);uj388iMQ}J-od+g z5AWjxe29&yZK`exYu?QB$ zVptqYU`Z^6rLhc_#c~*m<*@=*#7bBht6){EhSjkK*2G#^8|z?QtcUfn0XD=&*ch8& zQ*4IKu?4ooRv3n@u?@DxcGw;}U`OnPov{mc#ctRgdtguOg}t#4_QihK9|zz-9E5{$ z2oA+z7>>g+0!QFT9EGEC435QdI36e9M4W_^F%qZXRGfy>aR$!BSvVW#;9Q)C^Kk(# z#6`Fmm*7%dhRbmUuEbTi8rR@jT!-s%18&4kxEZ(LR@{c$aR=_iUAPR1D7VlAwVb+9hh!}{0&8)74Dj7_j9HpAxF z0$XA$48zvg2HRpgY>yqVBX+{h*af>{H|&l*uqXDy-q;8GVn6JU18^V?!ofHMhvF~{ z$Ke=(BXA^+!qGSe$Kp5~j}verPQu9;iBoVYPQ&Rq183qaoQ-pEF3!XGxBwU8B3z71 za49as<+uV@;woH?Yj7>D!}YiUH{vGTj9YLkZo}=k19##s+>Lv1FYd$rcmNOLAv}yn z@F*U`<9Gs3;we0hXYeeZ!}E9nFXAP8n18?Fjyp4D8F5biY_y8Z`BYccc z@F_mS=lB9&;wyZOZ}2U?!}s_BKjJ6+j9>68e#7th1ApQ#{EdI`FaAR%KK(}xbu`dK z3vF~zpo<>*7@))ujDk@y8b-$$7!zY*Y>b0(F&@Up1eg#LVPZ^zNii8F#}t?nQ(mq=6{}%&tbsML7S_f(SQqPIeQbaYu@N@LCfF34VRLMOEwL4b zVQXxIZLuA;#}3#LJ7H(+f?cs2cE=vr6MJEA?1O!=ANI!qI1mTnU>t%&aTtc88#yz+f_u+m#fCupq9>ybh6p!I? zJb@?i6rRR2coxs$dAxuZ@e*FfD|i*J;dQ)$H}MwU#yfZy@8NxXfDiEzKE@~b6rbU9 ze1R|V6~4wd_!i&cd;EYO@e_W=FZdO|;dlIjKk*m-#y|KM|DlqA{-cIE8fc=0HaaNK zMGt)pP+|y1!KfGwqhkz=iLo#?#=*E4594D3Oo)jvF($#Jm<*F+3QUQqFg2#Zw3rUl zV+PEKnJ_bE!K|1Kvttg-iMcR0=E1y}5A$OIEQp1$Fc!h0SPY9}2`q`Fur!vzvRDp7 zu{>75idYFNV->85)v!9&z?xVKYhxX(i}kQRHo%712peM)Y>LgWIkv!-*b2k2HMYUF z*bduc2keNQurqeSuGkH`V-M_!y|6d-!M@lJ`{Mu{h=Xu24#A-~48w6aM&Jk>iKB2d zj=`}w4#(pJoQRWfGDhMQoQl(MI?lkEI16Xv9Gr{ua6T@;g}4Y8;}Tqo%Wyfaz?HZP zSK}I7i|cSbZorMW2{+>w+=|<9JMO@pxC?jV9^8xja6cZvgLnuJ;}JZH$M86wz>|0i zPvaRpi|6n>UcifZ2`}Rnyo%TGI^MvWcnfdi9lVS8@IF4khxiB|;}d*}&+s|Ez?b+6 zU*j8mi|_C~e!!3T2|wc({EFZ3JO03*_zQpIAN-5|P)SJtQ9~UKG|@sE9Te!Ihdu@< zF$AMvRE&nvF$TuOSQs1QU|fuc@i74=#6*}FlVDOzhRHDnro>d38q;7}Oo!<)17^fb zm>IKRR?LRkF$dLkg}ZSN?!|q$9}nO`JcNhw2p+{_cpOjQNj!z8@eH2D zb9f#v;6=QIm+=Z-#cOySZ{SV5g}3nz-o<-(A0OaDe1wnj2|mSV_#9v0OMHc|@eRJk zclaJZ;79y~pYaQR#c%i>f8bC2g}?C+{>6W&B%=SQp^gTcXrYY`3Utv!9|M#af>AIk zM#JbB17l(=jE!+HF2=+7m;e)EB20`)FexU(!!pc|$ zt70{*jy13**23CY2kT-ztd9+_AvVIs*aVwmGi;76uqC#_Fl>!&ur0R3_SgYCVkhj3 zU9c;5!|vDvdtxu_jeW2$_QU=-00-hA9E?M7C=SDL9F7q<0!QK~9F1deERMtRH~}Z( zB%F+qI0dKTG@Onza3;>e**FL1;yj#>3veMW!o|1*m*O&9jw^5_uEN#02G`;`T#p-Y zBW}XYxCOW3Hr$Roa3}7<-M9z$;y&Du2k;;s!ozq3kK!>rjwkRWp2E|32G8O-JdYRf zB3{DFcm=QGHN1{D@Fw2E+js}>;yt{N5AY#A!pHaopW-uojxX>fzQWh|2H)a4e2*XS zBYwiq_yxb>H~fx2@F)Jl-}ndr;y+Xp(|^=ZM*~f?&_)LZy6B;g0ZI(PC>Rx^VRVdv zF)Wvqf#u^Lv# z8dwu+VQs8~b+I1S#|GFC8)0K?f=#g*Hpdp&5?f&yw#GKt7TaNa?0_Ay6L!Wf*cH2B zckF>Zu^0BnKG+xgVSgNe191=z#vwQqhhaDl#|RvOBXJat#xXb+$KiOKfD>^NPR2-_ zf>UuCPRAKI6KCOUoP%?59?r)FxDXfNVqAhtaTzYh6}S>t;c8riYjGW}#|^j-exUdJ1F6K~;dyn}b~9^S_X_z)lAV|;>7@fkkH7x)ri;cI+@Z}AfhJmLqk{rn^w7ruC5B)WjEd1PI>x}57z<-# z9E^+cFg_;0gqR2uV-ie?$uK#lz?7H@Q)3!Ti|H^uX26V?2{U6B%!=7CJLbTgm;O(V-YNh#jrS*z>-)BOJf-7)R4Xa}ftckU- zHrBzqSP$!C18j(murW5lrq~RdV+(AFtuPE*V;gLX?XW#|z>e4nJ7X8@irug~_Q0Ol z3wvW9?2G-dKMufwI0y&h5FCobFdTrgh>LJBF2SX^442~yT#2i2HLk(6xDMCj2Hc37a5HYft+)-h z;||=3yKpz|!M(T-_u~OPh==en9>Jq{43FapJc+09G@ik;cn;6w1-yut@G@S(t9T8s z;|;utx9~RJ!Mk`5@8bh}h>!3wKEbE>44>l*e2K5{HNL^O_zvIW2mFYi@H2kFulNnW z;}86azwkHy!N2$qm88o54HY%i(LfU|w9!FqLqPRxb5F%Ra& ze3%~#U_mT|g|P@0#bQ_-OJGSXg{83!mc?=yisi8aR>VqJ8LMDbtcKOG2G+z{SR3nL zU95-ou>m&3M%WmeU{h>{&9Mcx#8w!Ft+5TZ#dg>pJ77obgq^VqcExVk9eZF;?1jCt z5B9}=*dGVrKpcdFaR?5@VHl3XF#<>6NF0TuaSV>daX20);6$8+lQ9yf;8dK3({TpQ z#925S=ipqNhx2g(F2qH+7?_uyXKhx_pW9>ha<7?0plJch^d1fIlGcpA^(Sv-g5@d94NOL!Tt;8nba*YO74 z#9Me9@8Dg$hxhRTKEy}(7@y!%e1^~Q1-`^r_!{5fTYQJ_@dJLuPxu+X;8*;H-|+|j z#9#Ou|KMNzhe|T~j~eP|potdR=%7FsJ@hd^i6IyTqhd6SjxjJM#=_Vb2jgNqjE@O0 zAtu7am;{qzGE9ysFeRqK)R+d-VmeHZ889Pe!pxWjvtl;PjyW(V=EB^V2lHY+%#Q`I zAQr;HSOkk=F)WTHuq2kk(pUz|VmS=O@>l^YVkNAMRj?{n!|GTAYho>|jdidt*2DVP z02^W>Y>Z8?DK^9A*aBN(D-6Td*aq8TJ8X{~up@TD&e#RJVmIuLJ+LSC!rs^i`(i)r zj{|TZ4#L4W1c%}<49DRZfg^Avj>6G62FKz!9FG%lB2L1|7>QGGDo(@cI0I+mES!yV za4ycn`M3ZV;v!s(OK>SJ!{xXFSK=yMjcaf%uEX`X0XO0%+>BdrD{jN>xC3|MF5HcK za4+t|{dfQm;vqbYNAM^f!{c}YPvR*&jc4#Ip2PEa0Wabuyo^`yDqh3ucmr?ZExe6) z@GjoN`}hDK;v;;FPw*)|!{_({U*ao#jc@QRzQgzU0YBm={ET1lD}KZ8_yd39FZ_*v z@Gt&DB{}^^4RtioLsJnOoM4L9j3<&m=QB!X3T_y7RM4;5=&ueEQ4jS9EM_ftbi4<5?014SQV>bb*zCku@=_GI#?I$VSQ|X4Y3h6 z#wOSln_+Wofi1BWhGA=LgKe=Lw#N?G5j$aL?1Ejf8+OMY*b{qUZ|sA8u^;xw0XPr` z;b0tsLva{}<8X|?5jYY@;b@fE(tH~1Fc;d}gmAMq1@#xM94zu|ZMfj{vV{>DG}7yqG> zg8rk1IvQxAg*G}U&_xe@3{YYSM!~2U4WnZWjES)@HpaoY7!TuP0!)aBFfk^*1(!r3u|K?tc&%qJ~qIH*a#bA6Ksmj zusOECme>lzur;>9w%88aV+ZVrov<@@!LHa1yJHXRiM_Bl_QAf`5BuW)9EgK(Fb=_? zI1IyaI7Z+I9EqcFG>*ZsI1b0-1e}PIa56^X6r76Fa5~PwnK%n);~boe^Kd>cz=gO7 z7vmCKipy|0uE3SJ3RmMAT#M^)J#N5_xCuAo7Tk*4a69h6owy5k;~w0L`*1%Vz=L=Q z591L$ipTIcp1_lM3Qyx1Jd5Y>JYK+ycnL4#6}*bq@H*bWn|KRv;~l(<_wYVGz=!w< zALA2ziqG&lzQC9G3SZ+Je2ee!J$}HC_z6Gb7yOFf@H_s%pZE)Z;~)Hs|4>Ou|4~C7 z4K&e08yytrqK7^PC@}=1U{s8T(J=SbyT1i(0EQZCg1eU~7 zSQ^VU`?!rwXqJ?#d=sD8(>3hgpIKYHpOPx99v*Z zY=vRi8rxu7Y=`Z!19rqt*crQESL}w}u?P0VUf3J^U|;Nq{c!*e#6dV1hu}~ghT%9I zBX9(c#8EgJ$KY5ThvRVqPQ*z#86$BDPQ__B9cSQ7oQ1P-4$j4SI3E|_LR^H4aS1NP zWw;zy;7VMDt8opk#dWwIH{eFxgqv{-ZpCf59e3bP+=aVw5AMZ%xE~MTK|F+q@dzHp zV|W}-;7L4%r|}G)#dCNbFW^PIgqQIOUd3y89dF=GyoI;%4&KFkcpo3&Lwtmf@d-Y~ zXZRdn;7fdkukj7O#dr7~Kj26FgrD&Xe#LM29e?0Y{Dr^q5B|k}sHCF*sG*JqnrNYo z4hnS9LmvZ_7=lqSDn`TT7z1NsER2nDFfPW!_?Q3_Vj@h8NiZoU!{nF(Q(`JijcG6~ zro;4@0W)GI%#2wuD`vy&m;-ZSF3gR2FfZoA{8#`BVj(PyMX)Fq!{S&1OJXT3jb*Sb zmcvjij}@>YR>I0y1*>8;td2FXCf35*SO@E3J*iI08rF zC>)Jra4e3)@i+k|;v}4mkvIjX;xwF&GjJx(!r3?n=i)q^j|*@iF2cpQ1efA6T#hSn zC9cBNxCYnaI$Vz%a3gNQ&A0`(;x^olJ8&oN!rizB_u@X>j|cD|9>T+T1drk|JdP*u zB%Z?4cm~hnIXsUS@FHHq%XkH^;x)XEH}EFj!rOQU@8UhYj}P!6KElWN1fSwFe2y>h zCBDMf_y*tNJA98H@FRZ0&-ewu;y3(`Kkz61!r%A@|KdMXQqzCbP)7qzw9rNe1-j^= zj{!;y!N_irO}h_|tQtAxb;SSgq)kIrQNlvPvWF_+kzp|+{+mk^s#b`SIy|yXi~puZ z7E4m`e^;uCrs#^Hn2M#?ilYd{RXoL40!1nzN)#ok5>1JV@?v6R?K93`$2Pl>N2 zP!cMMl*CFBC8?53Nv@<&QYxwbJA^b!S|y#5UdfQblQd}valvGM7rIj*DS*4s3s+3nMC>51TN@b;rQdOy@ zR99*!HI-UQZKaM7sO1x+&e29!gK8m(pA5qx4n!DgBiJ%0Ok1GFTa+3{{3H;mU9&LK&fqR7NSI zl`+a#Wt=iznV?KmCMlDZNM(vLRhgztS7sY4LyjMObAC*taXXT6XRr#iTSAHlz zm0!wl<&W}L`S;)TtE#5zs-c>yrP`{a3e{CT)mH;msv&9=HL4m-jjqN}W2&*#*lHX# zt{P8`uO?6vs)^LZY7#Z6noLcurchI=snpbJ8a1t&PED_7P&2BT)XZuYHLIFU&93H9 zbE>)2+-e>*ubNNIuNF`Xs)f|TY7w=lT1+jjmQYKorPR`D8MUlhP7PJds}Hu}1I!GO?4pE1y!_;tfxEi63 zP)Dkx)Y0k~b*ws09j{JMC#sXw$!erJMV+cnQ>Uvl)S2omb+$T3ovY4M=c^0Uh3X=8 zvARTEsxDKPt1Hx%>MC`$x<*~Au2a{m8`O>JCUvvAMct}yQ@5)-)Sc=sb+@`l-K*|X z_p1lggX$smuzEy2svc91t0&Zx>M8ZKdPY5~o>R}O7u1XDCH1m;MZKzCQ?IKx)SK!p z^|pFPy{q0+@2d~ghw3BsvHC=PsyMQlN`bK@LzEj_;AJmWPC-t-XMg6LN zQ@^V})Sv1v^|$&*{j2`_@A0ders|h{hHAsK zaBa92p^eZ+YNNE#+8AxDHclI_P0%K4leEcNq&7vHs!h|TYcsT&+AM9hHbhsL_4Y-(~fH=w3FH??X-4AJFA`3&TAL6i`pgavUWwgs$J8r zYd5r;+AZz2c1OFb-P7)C544BcBki&FM0=_|)1GTDw3pf|?X~tsd#k6!H`dR9G~o?XwO=hSoQx%E7HUOk_lUoW5+)C=i_^&)yvy_jBHFQJ#zOX;QcGJ09P zoF1x|*DL51^-6kWy^3B{uclYmYv?ugT6%50j$T)FxCndPlvJ-dXRWch$S;-Sr-NPraAkTkoUy)%)rF^#S@oeULs_ zAEFP{hw0(^a6LjFp^wx@>7(^A`dEFOK3<=oPt+&rll4e_iau4Jrcc*r=ri?M`fPoU zK3AWo&({~|3-v|%Vtt9eR9~hq*H`E(^;P<6eT}|WU#G9vH|QJnP5Neii@sIgrf=7G z=sWdY`fh!XzE|I;@7E9L2lYexVf~1HR6nL4*H7pt^;7z3{fvHAKc}D9FX$KbOZsK~ zihfnUreD`@=r{FS`fdG=epkPz-`5}L5A{d-WBrN#RDY&F*I(!_^;i09{f+)sf2Y6K zKj_LBsWqRDUDP{Y9o!2)<|ch zH!>I*jZ8*nBa4yM$Yx|Wau_*{Tt;pqkCE5NXXG~u7zK?&Mq#6fQPe1A6gNs3C5=)> zX`_r$)+lF$8s&`&Mn$8NQQ4?sR5hv@)r}fPO{11k+o)sIHR>7ljRrl@VsNHrg0%jdn(Rql3}W=wx&@x)@!JZbo;bhtbpMW%M@s7=4X?Mt@^~ zG0+%f3^s-sLyciZxG~&_Fh&?7jZwyEV~jD@7-x((CKwZqNycO&(wJgQHKrNUjTy#F zW0o=7m}AT}<{9&i1;#>Sk+IlVVk|Y58Ox0o#!6$AvD#Q;tTomd>x~V@Mq`t)+1O%i zHMSYsjUC2LW0$ep*kkN9_8I$)1I9t)ka5^JVjMM&8OMzi#!2IpaoRXzoHfoF=Zy=- zMdOlj*|=g{HLe-gjT^>I#nZQhFCNdM7Nz9~XGBdfE!c1wVGEn|aKo47BUN)Ma-gRF|)W? z!YpZ)GE19f%(7-VGt?|^Rxm4?mCVX!6|<^Y&8%+LFl(B%%-Uuhv#wdstZz0j8=8&G z#%2?y%**B#^Qw8xyl&nwZ<@Ev+vXkfu6fVAZ$2;|nvcxK<`eU&`OJK7zA#^! zugurx8}qIC&U|lvFh81~%+KZ*^Q-yI{BHg*f11C{-{v3luldhZEY;F1-7+lGvMk$j zEMd8pXZcoONh`#PVnwy0S<$T+R!l3F72Aqq#kJyD@vQ__LMxG#*h*q0wUSxMtrS*D zE0vYnN@Jz9(pl-P3|2-fla<-ZVr8|mS=p@|R!%FImD|c=<+buz`Kj|(W+!swyIcFt!h?vtAR5HHdRBd_ zfz{AzWHq*$SWT^FR&%R`)zWHZg;}kwHdb4!oz>pzV0E-QS)HveR#&T=)!pi0^|X3f zy{$f0U#p+h-x^>Iv<6v&ts&M>YnT;o4Ywk!5!Og+lr`EKV~w@OS>vq<)chqcq%W$m{1SbME~)_&`Nbw^)_v=N_0W1`J+_`$PpxOx zbL)lm(t2gRw%%B8t#{UY>x1>t`ec2!zF1$aZ`OC~hxOC?W&O7PSbwd5mSU^6X6v?L zo3>@!wqpz1wLROn16$f5b`(3R9nFqz$FO7CvFzA(96PQZ&yH^=uoK#e?8J5wJE@(_ zPHv~LQ`)KQ)OH#>t)0$JZ)dPG+L`Rkb{0FUoz2c}=dg3yx$N9_9y_m{&(3cbunXFS z?80^tyQp2vE^e2wOWLLE(smiUtX<9ywaeQT?22|JyRu!yu4-4atJ^i~nszO_wq3`r zYuB^u+YRi7b|bs7-NbHcH?y1DE$o(dD?7|?ZMU)8+U@N2b_cto-O283cd@(L-R$mm z54)$`%kFLWvHRNn?Edxud!RkY9&8V>huXvJaC^8NVUMs!+N12z_85DtJx+YEQGL+cWH$_AGn0J;$DF&$H*-3+#pVB73pD#9nGIvzOZ|?3MN^d$qmB zUTd$j*V`NHjrJycv%SUMYHzc*+dJ%?_AYz3y~o~b@3Z&Y2ke9PA^Wg>#6D^tvya;+ z?34B>`?P(=K5L(|&)XO5i}oe^vVFzAYG1Rj+c)f+_AUFieaF6Q-?Q)A5A28bBm1%a z#C~c&v!B~9?3eZ{`?dYXervz8-`gMTkM<|~v;D>XYJao8+du4|_AmRl{m1@m|FacG zbu>qJ499dV$95b?IIiP4z7sgo32~x0QJrW`bSH)r(~0H8cH%g3op?@sCxMgDN#rDU zk~m47WKMD?g_F`r<)n7fIBA`9PI@PUlhMiKWOlMRS)FW7b|;6E)5+!JcJerRoqSGy zr+`z?DdZG(ia14`Voq_Vgj3Qf<&<{HIAxu3PN-Afso+#}Dmj&%Do$0Wnp54W;nZ|$ zIklZSPF<&-Q{QReG;|s{jh!Y=Q>U5J+-c#obXqxKPHU%))7EL{w0Al<9i2{2XQzwP z)#>JRcX~KIonB6Fr;pRu>F4x!1~>zqLC#=jh%?j~=7c-Lod{=yGtwF5jCRI2W1Vr% zcxQq$(V65-b|RfA&QxcbGu@ft%yecsvz_oh8mvXPL9yS>dd7 zRynJkHO^XRowMHA;B0g@Ih&m=&Q@oev)$R@>~wZHyPZAGUT2@P-#OqMbPhR(og>as z=a_TcIpLgiPC2KYGtOD(oO9l};9PVrIhUO)&Q<4{bKSY&+;na^x1BrAUFV*2-+ACX zbRId6ohQyy=b7`|dEvZtUOBIwH_ltq#Dz=I3Vu#o%c8T3$kJu~riT&b$I4BN@!{UfIDvpWc;)FOUPKndvj5sUKiSy!u zxF{}(%i@Z-Dz1s^;)b{>Zi(CCj<_rCiTmP#cqkr;$Kr{2DxQhw;)QrAUWwP@jd&~G ziTC1z_$WS!&*F>tD!z&D;)nPteu>}WkN7M83B^@i&DCAQHC@ZKUB?x!>w2#52Cj5N z+$e5TH<}yWjp4?0W4W>2IBr}wo*Umy;3jkvxryB*Zc;ayo7_#|rgT%esogYgS~s1W z-p$}XvsaxE0+>Ze_QMTh*=RR(ET-HQicnZMTkF*RAK)cN@43-9~O>w~5=- zZRR$2TevOVR&JQv+HK>ub=$e^-41R?x0Bo1?c#QIySd%n9&S&!m)qOz2L+)YshS>q%j{+GvU=IP>|PEprj4l@@ji^yt-aJufEs7Yv?ud z8hcH=rd~6zx!1yL>9z90yw+YDudUb4YwvaNI(nVF&R!R_vK0ys6$a zZ@M?bo9WH+W_xqIx!yc)zPG?z=q>UVdrQ2f-ZF2wx58WLt@2iTYrM7II&ZzV!Q1F< z@-};0ysh3gZ@ag{+v)A{c6)ogz1}`=zjwep=pFJ7dq=#Z-ZAgEcfvdAo$^k5XS}oC zIq$r8!Mo^P@-BNs!Taca@;-ZCyszFj@4NTI`|17ietUnszurGj@l{{*b>Hw!-|}tW z@rCdDp6~mCFZ~cdiXYXF=12Eq_%Z!her!LEAJ>oP$M+NX3H?NVVn2zW)KBIo_fz;O z{ZxKxKaHQ(Pv@uiGx!<(OnzoRi=Wlc=4bbF_&NPter`XHpV!al=l2Wv1^q&PVZVr9 z)Gy{2_e=OC{Zf8uzl>kjFXxB)<^2kNMZc0?*{|YP^{e^S{ThBvzm{LyujAMC>-qKl z27W`ok>A*F;y3l1`OW!Rv_J{aG{b7E%KirS-NBATCQT}Luj6c>N=a2U%_!Ip}{$xMW zpW;vTr}@+U8U9RvmOtB{%zv5r@uld*g8~#oImVev7}|C9gO|Kfl3zxm(&AO27Om;c-UK2m%>|1W|&hL9`%x5F>~g#0p{uae}x(ydZv%AV?S_3K9oNf}}yR zAbF4?NExIGQU_^*v_ZNceUKr@7-R}E2U&uwLAD@!kR!+$fLrN(W_vvO&2ZG$^bPKu% zJ%XM=ub_9(C+HjW3;G8Gf`P%HU~n)b7#a)2V;V*2 zObR9kk-?N;YA`LB9?S@42D5_M!JJ@jFfW)NEC?0`i-N_$l3;1DELa|_2v!EGg4MyA zU~RB2SRZT%HU^u5&B2ynYp^ZY9_$Eq2D^gY!Jc4murJsj90(2uhl0bwk>F@>EI1yV z2u=p4g44m7;B0U%I3HXHE(Vu^%fXf4YH%&M9^43S2DgIS!JXi4a4)zYJO~~JkAla+ zli+FaEO;Kg2wn!Sg4e;D;BD|OcprQSJ_etH&%u}AYw#`j9{dP?2ET&e!Jpu7@GnrL zDmAG~Lz>c(wsfSBuJoiY11V*Qj3T4TXfnEtA!Eu|GPaB(%*z04pp%1kn|%p$YOY%;sdA#=)HGPlel^U8cOzbqgN%0jZR zEFz1_VzRg_Axp|qva~EC%gS;xRF;<&WJOs?R+d#{Ras3|mo;QfSxeTIb!1&xPu7JIT(ni|i`9$?md;>?wQ6-m;JE zEBnd*a)2Bt2g$*5h#V@1$#6MbM#vFzq#Pwj%Q14S94E)i338&GBqz&AIYmyD)8uqH zL(Y`5l#k?N`9waI&*XFYLcWx*NO1=WV?Ky{&dP<^NY z)DUU}HHMl%O`%|@859CFhgv|PP#Dw_Y6aP#aL5j|h9aOyC<=;(VxTrqEEEUDLkUn@ zs2$WE>Hu|wIzgSGE>Ksf8`K@@0riA>LA{|qP+zDY)E^oE4TJ_k4rnkm1R4qrgN8#R zppj4_GzuCGje*8O!9_}252L+3EB*8fwn^1pzY8OXeYD_+70c2_Couh z{m=pEAan>i3>|@vLdT%v&fwRKd;OuY?I47J7&JE{*^TPSy{BQxdAY2G83>Sfm!o}d? za0$31Tna7?mx0T|<>2yg1-K$y39bwW!d2j^a1dM#t`66LYr?hQ+Hf7XE?f_;4>y1t z!j0g@a1*#G91J&uL*V9c3pf-GgImI_U>h6`+u_!51RM!R!O?IG+y;(?A9i!K2|Z@K|^pJRY6^PlPAIli?}wRCpRZ9i9QtglECC;W_YJcpf|-UH~tI7r~3+ zCGb*s8N3``0k4Et!K>jl@LG5sydK^FZ-h6&o8c|+R(Kn{9o_-&gm=Na;XUwPcptnU zJ^&wt55b4wBk)o97+04UxY8gm*Fe$Rrnfw9linIgm1yO z;XCkM_#S*8egHp&AHk2|C-77F8T=f60l$P_!LQ*r@LTvD{2u-Qe}q55pW!dC8*m36 zz!P`@Z{P#cfV99Dqyy=JAIJbQ0tmnW00f`_12`Z62`E4V2C#qwJP?2gBp?F?s6Ycc zFn~V@0GU8$kOgD~*+6!X1LOp`KyHu+Hb* z&;#@Yy+Ci!2lNH~Kz}d*3 z5`{z~F-RLE7KuaRkp!eI(hg~lbU->HosiB*7o;oF4e5^bKzbs*klsiiq%YDB>5mLR z1|owH2QnBLf(%85A;Xao$Vem+8HJ2S#vo&namaXN0x}VqgiJ=JAXAZP$aG`|G837F z%tq!QbCG$-d}IN#5LtvQMwTE;k!8qoWCgMkS%s`d)*x$T1F{j>gltB(AX|}b z$aZ80vJ=^b>_+w=dy##}e&hgh5IKY#Mvfpykz>el1M(61gnUN6Aa1BT>VbNqUZ^+fgQh{#qP}Q4G(GBvW4Eigl0ywpjpvuXm&IQniI{1=0@|NdC`1m zezX8u5G{ljMvI_D(PC(Ev;0*yqY z&}cLUZG*<5acDf6fVM^3q3zKQXh*aY+8OPFc163P-O(OsPqY`>8|{PkMf;)s(E;c{ zbP(!52ctvKq3AGlI649yi6)|>(9!4^bSyd!9gj{xC!&+k$>)+&FB_%E4mHc zj_yErqPx)D=pJ-0x)0rt9zYMGhtR|55%ef}3_XsXKu@Bl(9`G{^elP~J&#^MFQS*w z%jgyKDtZmQj^03TqPNi7=pFPfdJnyiK0qI$kI={H6Z9$i41JEiKwqM-(AVf2^ey@h zeUE-XKcb(|&*&G_4RgmlFi*@2^TvFzG+0{97fXkw$NaDiSVj!OU<_aghGH0oV+2NG z6h>nV#$p`CV*(~(5+-8`reYeVV+Q7r1z?%5%vcsIE0zt*j^)5|V!5!~SRO1dmJiF1 z6~GE&g|Na{5v(Xy3@eV6z)E7Ju+mr=tSnXzE00ycDq@we%2*&)1*?h$Vb!qeSPiTu zRtu|*)xqjw^|1O_1FRv|2y2Wr!J1;hSTigHYmT+RLa{KcCDsbFVd0n^YmG%2%eXzb*Kde7C02_!6 z!W`IOYzQ_K8-@+XMqnecL~Il`8XJR+#l~Udu?g5jY!WsZn}SWnreV{u8Q4s07B(B3 zgU!X}Ve_#C*g|X(wisK2Eyb2$%dr*MN^BLj8e4;{#nxf#u?^TpY!kK_+k$Pywqe_` z9oSB67q%PQgYCukVf(QI*g@=Jev zyMkTCu3^`)8`w?k7IquEgWbjMVfV2I*hB0Q_85DDJ;k13&#@QSOY9Z)8heAi#ol4> zu@Bfs>=X7G`+~XQ?zji;iF@JRxDTENPmBBF>G1TpAD#ivh(kDx102Co9K&&(z)76K zX`I1XoWprsz(ribWn95kT*GzT!2R(6JQJQ7&w^*gv*FqC9C%JV7oHo>gXhKb;ra0b zctN}nUKlTe7sZR=#qkn&NxT$Z8ZU#F#mnL4@d|iFyb@j+55%kBRq-Ia8eSc*f!D-q z;kEHPcwM|6ULS9OH^dv^jqxUUQ#=@NhKJzI@fLU}9)`EXTj4f59Jk}G@d!K;kHVwz z7`zQ0i^t*dcmm!QZ-=+XJK!DhPIza$3*Hs)hIhw%;63qPcyGK9-WTtO_s0j|1Mxw) z10ReJ!H43*@ZtCfd?cQTkHSaeWAL%~ID9-l0iTFZ!YAWX@TvGTd^$b@pNY@HXXA75 zx%fPMKE427h%dqy<4f?R_%eJsz5-u~ufkX3Yw)%BI($980pEyk!Z+hv@U8eZd^^4a z--++ScjJ5Tz4$(SKYjo|h#$fa<45qL_%ZxAegZ#SW{5pOEzlq<%Z{v6HyZAl)KK=lIh(E#~<4^FX_%r-D{sMoAzrtVRZ}7MHJN!NV z0sn}9!aw6*a5uu8@E|-1FT$JfA<__O311=|k)H4)G7uRFh=2(|AOuQa1WphHNl*k$ zFa%3*1WyQrNJxZCD1=IAgiaWQKM_D=A~F+Mh^$05B0G_T$VucPaua!oyhJ`CKT&`v zNE9Lp6Ge!kL@}Z`QGzH*lp;zKWr(swIifsKfv8ASA}SMsL=~bc5kyoYsuMMcnnW$4 zHc^MDOVlIk6Ag%lL?fax(S&G91QX4O5TZHJf(RwTh?Ybv!bXG>cA_;AK|~T!L^KgY zv>{@NI3k`%Alee`i1tJWq9f6X=uC7Wx)R-p?nDowC((=OP4pr968(t&!~kL-F^F&w zgNY%;P+}M{oESljBoc{H#Asp+F_sudj3*`#6NyR0WMT?2m6%3MCuR^ciCM&KVh%Bv zm`BVf77z=GMZ{ua39*z|Ml2^*5G#pQ#A;#4L&Ra?2yv7+MjR(j5GRRK#A)ITah5nooF^_27l}*6W#S5P zmAFP+CvFfoiCe^N;tp|_xJTS49uNW#B1UW@s@Z;yeB>o zABj)IXW|RtM!J(8q$lY`dXqk68Zs^EOQs{!lYV3dG9w9*FbPP6L`jUqNrEIvilj+~ zWJ!+XNr4neiIho&R7s80NrUt!1ISEdW-<$zmCQzFCv%WF$y{V^G7p)T%tz)Y3y=lL zLS$jG2w9XYMiwVakR{1dWNEStS(YqEmM1Ha70F6uWipVgLRKY%$ZBMDvIbd`tVPx) z>yUNHdSrdF0ojmjL^dXykWI;8vKbjdHYZz_PS~av`~hTud$@my*lK<>U%-CAo@RO|Bu=lIzIz;#3K$BvpzkO_ia_Qst=fR0XOcRf(!h1yWV0s#FkFjjB%7plVXJsM=H= zsxDQJs!uhb8d8m@##9rkDHTjLqe7_WR0}GU3Zq(5ttcB6PT8r}R0I`CMN!dI4Aq8; zrQ)b~DuHTCwWHco9jJ~}C#o~mh3ZOmqqPz*b`cng_fz%+%K@Fyc zP(!I<)NpD9HIhoCMp2`wG1ORU95tSrKux43QIn}D)KqF3HJzG4&7@{gv#B}MTxuRQ zpISgIq!v+&sU_4>Y8kbhT0yO(R#B^|HPl*a9krg?Ky9QpQJbkP)K+R6wVm2Q?WA^5 zyQw|YUTPn;pE^Jtqz+MssUy@;>KJvLIzgSJPEn_+Gt^n?9Ce<$KwYFRQJ1MJ)K%&l zb)C9F-K1_&x2ZeSUFsfnpL#$&q#jX^sVCG^>KXN%dO^LUUQw^9H`H6|9rd32Kz*b> zQJ<+VlpF0%d(fV=7wt{^&}rzjv@e~GPEY&M8R(2OM8h=cIGdx#>J~UOFG0pDsWbqzlo7 z=^}Jdx)@!YE~K|x)I%&ZbCPugXv~;2;H1+L5I>|bW6GwZKK0!JKdU&pd;xhI+~85+t9Ie z934+5&~523(#7dH_9;9z;9n!SoP% zC_RiGPLH5R(uwpadNe(T9!rm-$I}z&iS#6TGChT!N>8Jw(=+Iq^elQdJ%^r4&!gwl z3+RRPB6=~sgkDN7qnFbw=#}&;dNsXv&$^e%cg zy@%dQ@1ytA2k3+JA^I?Vgg#0iqmR=k=#%s*`ZRrpK1-jY&(jy^i}WS>GJS=Lgw^ey@}eTTkF-=pu-59o*VBl`ZfKAeoMcj-_sxHkMt+{ zGyR2jW84`J#*^`4ycr)R4U?AfWzsR}89ycilaYZKm;nsJpbW;~48f2L#n24HunfoW zjKGMD#K?@osEo$wjKTOb0Zb+)Gn0kM%4B1*GdY-?OfDujlZVO6 zgel4tV~R5+n37B>rZiKADa({&$}<(1icBS@G84#DVX87gOf{xDQ-i6=)M9Egb(p$L zJ*Ga>fN97yVj43|n5IlH(~JpWnlmk!P$rCN$+Ti@OgLj_S~C$$BooC%GcimXCYFg~ z;+X`dEz^!^&valqGM$*tOc$mr(~arQ^k8~2y_nuiAEqzUkLk}0UHapnYbk~zhkX3j8YnRCo}<^pq(xx`#%t}s`bYs_`# z26L0S#oT7@Fn5`I%zfqo^N@MOJZ7FSPnl=TbLIu}l6l3vX5KJwnRm>4<^%JQ`NVu? zzA$dAJL|!EvR%*pD)3Uy7IyOD)$7WzNvJeZifJIo8#aNsrSdyh!nq^p)Y@owgOv`t;AMl1KBEURW^vN##U!*ur=9QY;CpjvdcVU?;MZ*vae^b}BoKozBi+XR@={+3Xy4E<2B%&n{pW zvWwWo>=Je=E`TdyGBKo?uV1r`Xf%8TKrDjy=y_U@x+l*vsq{_9}agz0TfX zZ?d=8+w2|oE_;u?&pu!uvX9uu>=X7W`;2|gzF=Rnuh`e@8}=>xj(yL5U_Y{-*w5@2 z){S%LJUCCzi}U7uxHMc^&X-HarRV&(3|vMI;$RMN2#0bQhjRo+aui2%499XD$8!QF zauO$V3a4@!r*j79&joOqxXfG@E-ROf%g*KCa&o!2+*}?mFPD$Y&lTVba)r3UToJA) zSBxvpmEcNprMS{u8Lli>jw{bq;3{&JxXN50SB0y}1##85>Rb)3CRdBA&DG)Ra`m|S zTm!Bl*NAJ(HQ}0a!CW&gglo>V;6k}Dt|ixsvvJ{^oomfSaFJXT7tO_RZMax2j*I6K zxVBt7u07X*>&SKDI&)pPu3R^+JJ*Bj$@SuTbA7nJTtBWqH-H<+4dNW!U~ULElpDqk z=SFZNxkPRhH<}y6jpfF18bz z5x1CI!Y$>Nam%?C+)8d0x0+kSt>xBn>$wfwMs5?gncKo`<+gF#xgFe2ZWp(k+r#bU z_Hp~U1KdIG5OdpRbJzD-r)WD06r6+na{#!<+JhG`5b&sJ{O;x&%@{C^YQul0(?Qf5MP)t!WZR> z@x}QPd`Z3(Uz#t&m*vax<@pMHMZOYWnGfWv@KyOBz8YVhuff;kYw@-DI(%Ke9$%kt zz&GR@@s0T=d{aJ{Z^no4&G{C5C?CeR=cIDdja$)Dm+^Jn<8{5k$Se}TWqU*a$GSNN;^HU2t(gTKk& z;&1bJ_`Cc){yzVJf5<=LAM;Q6r~EViIsbxx$-m-X^KbaK{5$?V|AGI=f8sy$UwAjc zUGNY*1uwx{@Db7oX$4;)oseGe6EX-H1xSDeARq!NU;-`>0x3`eEieKrZ~`v~f+$FW zEGU91Xo4;ng1-I)5o zhC(BuvCu?lDg+D7gb<;*&_W0m!i1JWE5Rm&3wEKk5FtbgQ9`s3BeW4>g*YKzND$fz z?S%G12ce_TN$4ze5xNT9gziEQp{LMG=q>aS`U?Go{=xuZpfE^q2!n+o!cbwDFkBcR zj1&@uQNn0pj4)OhCyW;+2or@#!en8JFjbf)Oc!PdGlf~gY+;TtSC}Wv7ZwN$g+;<* zVTrI*SSBnNRtPJFRl;gvjj&c&C#)AX2pfe>!e(KMuvOS5Y!`M2JB3}sZefqGSJ)@) z7Y+yqg+sz&;fQclI3^qyP6#K3Q^INCjBr*sC!7~92p5G*!e!x#a8-P{7VC(0#d>0Wv4Pl7 zY$P@on}|)tV6mAPA~qLWh@oPb*ivjI+Qe|tF18jU#7Hqpj22_WHe#$8C&r5jVq3AD z*k0@)b`(2_oy9Iir2*J;tlbpcuTx3 z-VyJL_r&|+1M#8wNPH|l5ub|B#OLA*@um1md@a5a--_?V_u>cfqxebuEPfH)BzMU} z@|3(JZ^=hWBc+vmrF2qy$xq54Wt1QZmVksvsDw$lL`bAWNwmaBti(yYBuJtpNwTC! zs-#J}WJvx}fRstfEM<|hO4+3BQVuDnluODj<&pAA`K0_(0jZ!=NGdE9k%~&iq~cNu zsiag&DlL_f%1Y&=@=^t4ENDMd-qQjF9_ik0G|cqu_@E47o_ zOC6++QYWdi)J5tlb(6YFJ*1veFR8cGN9rr}lln^oq=C{P$srAvhDbxDVbXAEgfvo0 zltxLTr7_Z2X`D1(njlS-CP|Z}DbiGFnlxRSA6~<4x*%PYE=iZAE7Dcznsi;dA>EX2Nw=jt z(p~AEbYFTPJ(M0vkEJKlQ|X!XTzVnBlwL`%r8m-B>7Ddm`XGIjK1rV?^S@Wh?y`sM zDSOG@vX7ibPAmJ$>E!gXpPWI?C_^$V0~wJ~8Iy6DkV%=6X_=8(nUi^0kVRRNWm%C` zS(A0ykp1NVIg^}O&LU@(v&q@z9CA)Mmz-P9Bj=U#$@%31azVL}Tv#q57nO_2#pM!m zNx76CA1LZ-oLmn&-k%!8|^FR`FHRDd`nIC4-VtffQH)3ZkG2rr-*pkP4;H3Zt+Jr|^oPh>E1hilV5B zrs#^H_$vWQCMC0yMaimUQ?e^Ll$=T~CAX4C$*bg3@+$?Df=VH!uu?=RsuWX-Dsj1XbYAbb=x=KBzzS2Nxs5DX< zD@~N9O0d#Q2~nCWEtF6tOlhgKQfx}NVpm!#5lW;Ir9>++N*g6siBsa01f{LgPHC@n zP&z7|l+H>QrK{3S>8|updMdq?-bx>(uhLKHuMAKIDuWb)U~Q@N$wR_-Ww zm3zv4<$>~0d89m6o+wY1XUcQsh4NB)rMy<&C~uW_%6sL5@=^Jud{)f=UZc9J9;&D6 zrFyGAY8o}I>Z_(x)2n`J1~sD!sjv!EL`79h#Z^KjRZ68*MrBn_<5lIn`WhZZ(gZSIwv9R|}{G)k11vwTN0&Ev6P%OQ~@eR_myB)p}}uwSn4DZKO6< zo2X6IV6~YVqBd7ssG(|@+EQ($+SG8>uC`Vq)JQc-jaFmSHfpRIr^c%ZYFo9P+FtFT zc2qm5oz*UCSGAkkUG1UvRC}qt)jn!pwV&Ew9iR?W2dNHqusTE?st!|!t0UBrYN9$y z9j%U0$ExGh@#+M1qB=>PtWHsI`+JI!m3c&Qa&8^VIq30(GIfNL{QhQJ1RA z)aB|5b)~vWU9GNB*Q)E(_38$7qq<4mtZq@as@v4<>JD|Mx=Y=y?os!u`_%pF0rjAI zNIk3`QID#})Z^+2^`v@AJ*}Qm&#LFt^XdilqIyZatX@&Cs@K%(>J9a#dP}{n-cj$W z_tg991NEW$NPVn6QJ<>M)aU98^`-hseXYJx->UD__v#1rqxwnxteXG5Ky%kTG*8V- z^VWQ{G+J8CS4*d**Zi~$T1E}hU=3)9hH99GYlKE>ltyce#%i3#Yl0?fk|t}4rfQm| zYlh~p1!$SH%vu&LtCmg6uI12jYPq!BS{^O0mQTyC70?Q5g|xz25v{0JOe?OH&`N5h zw9;A`t*ll~E3Z}1Dr%Lq%37dSMXRa>Y1OpqS`DqHR!gg`)zRu|^|bn01FfOfNNcP$ z(VA+(S~D#~Yp%7>LbWihrPfNbY2lh(Ypq3Sky?}%t;J|KdrwuKpUtH(j3}gZHP8h8>S7{Mrb3oL~WEd zS{tK{)y8S#wF%lpZIU)wo1#tCrfJi)8QM&3mNr|Pqs`UkY4f!O+CpuSwpd%DE!CE3 z%e58SN^O<4T3e&7)z)e2wGG-vZIiZH+oEmNwrSh79okN9m$qBmqwUr9Y5TPU+ClA* zc33;29o3F$$F&pMN$r$&T05hi)y`?>wF}xs?UHs`yP{pyu4&h`8`@3nmUdgaqutf+ zY4^1U+C%M;_E>wOJ=LCR&$SoYOYN2RT6?3t)!u3EwGY}y?UVLd`=Yt&?z)HWse9?( zx{sblPpkXt>GbrvpPoU_s6#re10B&(9n*20&`F)rX`Rtoozr<;&_!L+WnIx#UDI{l z(EarQJ(HeU&!T75v+3FO9C}VYm!4bCqvzH0>G|~ndO^LAURW=p7uAdD#q|<;NxhU_ zS}&uQ)ywJS^$L1Ly^>y857evZRrMgfnqFP6q1V)F>9zGbdR@JqUSDsZH`E*HjrAsa zQ$1L3ribXw^%i=l9;Ua{Tj@4ET(|43^$0yukJ6*{7`=@itHP7{JLnzt zPI_m(i{4f5rgztS=sopbdT+gt-dFFZ_tyvL1NA|=Lm#XU(TD28^x^sleWaeKkJ3l$ zWAw55IDNc6L7%8k(kJUv^r`wZeY!qFpQ+E%XX|tHx%xbPzP>r3>d`Z9gF zzCvHAuhLiRYxK4HI(@yqLEorv(l_f{^sV|feY?Ix->L7?ck6rfz4|_VzkWbJs2|b~ z>qqpX`Z4{uenLN~pVCk3XY{lBIsLqTLBFV9(l6^*^sD+c{kncbzp3BSZ|isTyZSx- zzWzXes6Wyl>reEj`ZN8x{z8ALztUgpZ}hkNJN>=>LI0?K(m(58bT`A@@Gv|LFT>mL zG13@m4PPUjk>2n#G8h>R$bb!CAO>n+25t}rX;21jFa~RI25$(4Xh?=^D28fihHe;! zzY$<$GBO)kjI2gBBfF8q$Z6yW z7%Poc#%g1YvDR2;tT#3o8;woIW@C%7)!1fiH+C31ja|lWV~?@d*k|lF4j2cGL&jm_ zh;h_7W*j$87$=QW#%be>an?9zoHs5Q7mZ8CW#fu*)wpI{H*Od=ja$ZTfNescyKSP`jH++l zovD1|!z07u;zJ^1%pBh|AwJG*NutBI_SXa>69=YsbK8CQyw52lD9FlgcE(k;t261! zhQxB zB!tAqhsAzv?Dg-<$Y3f#Y>_3KpGozge8*ZaB<%M`jrL4xX6{t^|0ttR`3CuN=gVW& zT_{yvhi}`E*l=^`h>l8hctw~1P69wNGm*Wxb-H5;BruIr(=7Ysx^?&^4MxEc?V=;i zQXNngGqa_-)mpk#>l}iEEPK+A4xd`CR9|J-5GQD&{b5K8iLUc{@MU8)BCE{nvQ|z0FVc79P;V?8tbtBSH2C z-!jw5aKAVgVEw+bAh5mV(b#*NiN8CUo>g$TG5Hfa+lOY@mm_JZcg9HG1eoxYd7i1n@GWdf2NYd1H%V|1NUh?`qzdas!lT`_U&bBlUp&sOR*VkQ4? z1LO8oY~1iuHhM>fw+{>bAK;`@fh#z@3iD+8wDU}iTJw(~hYybL4P5gNTZsxKlalYNaNFf&sktRf>rICljEc)eh^OY72 z8urHHv`Ht)jv6lL*akYhVmg>Db$A5Fm_|0hk*-dy94-enDzWm^^)e<2GN5uj!kZC&-3;^J#bI;9=S< zZ#Or0cc<0*MG|*6cMnS+!)yUTwoF!WKK9xsOr27*1^&#AdYC5h^?)+4naX*Xop2sj zF;3onQ$&@Y6=7Lbvk1!Mh4b)AqGpqw$Je65Y}Ktc1=^~FeHF`32g`X*`;(O2NuHKS zx-03TX_F{i(kk6)fBt`E;%^70Z-)0usSqn$*WV9Jwz_|U*xwB5zZsSOT)uC+eUW?|%x{hR3{HOc>R*p6qED{f&w9qB#pAklvglwhZ2uXjPiGq#WE&LZ z8Un1#|LVB=vvym$znStusYd_l-+DW~rY~WR4i%GRn?(M3^miq|O1dbOL<5p&X3IOM(Cw}3 zSKE?6^GibC{p*}S%K=(Fpo|kuer7@oM*p130-ps`E0r}p?`!`3(ex*&&%c!?lvGU6y4$5JU;HH zqD!qXsjq)v8)UNWzdX5Y@8)(y*jMTmaB~xuHW_Bo$WqTQTXf^`zS&-zG<_)B*`jaB zbFJQ(w8Ff|)fOFH*u(p+NpBq(UCW|*n`D`8Q5oDD{LZ8?)4N=>=$1T4miH#jxNCL? zi#DlSWbb?66z+G1?GNI>{}19dwQy5k|4h*K@+O1^l=n}>6%nQ9)H)ErxmpUnVM+G7UeQn$zD zd)dnUEK^DZFVNu~Z;K5JOEf2jrh#*6Thd%Hhpn7zt{7%97lAUMDTm_2m>*9_b_ zW5vz@?hXsK-==U6^hljzpOqra72+M9401ChnW05Ue6YFdAKWq`+N|F0Fn>!PIg&tX zA8J1Pk8%2&shlg~`cD$~G$n3IigHQX42vXPBumPY%)b*$cK8Ih2x(!n6x!hx6&fDt z+$qr1tnf!9eivF!-d*t*5J{2vA3`JrQ@@4CcL4Zo3H@7$m=Vp?5b-dBH^~rroC+eo z&Twn6a|-w$!{b-euRn~}{S!$3&Mdg13_l9>pUuMe;7h*$g?afJ9d&y!_1!Sc^hpZm z9&*K^19m9cag&~{y{5B8{ci^zz+Jw|4;IhEY4M!K-{I9VIy#pLO_StykvuMv*G2Lr zrjA$NH5KH$`1D_(GX-pah|a%;bIezZ_v;!`2Ak+qL4)6_z;^(zU;+GF75Gj`s{WY5 zGteuwlGOS!g-4)Ipht==Zjefi3Ru^c18-VdKQlV-(xmu~W>nxm)}$1keCrVW#?ZRL zbWRlf(g*w|wx1l+|21_g!a#d^a|ZG?jPS>GY#kI#zwt!fenXS$NMqI!;P5O{u6ANo zIHR>%U|qUa3tRz={~4VBEd-bS2%qmDxH%PkCZvK-hGh8UHrIIm4g93|V$N{fS1;^u ztneR8_v@&QR?<->#R>EN`FI>Q0i@>)ZrCer*;{0l`E6AlJ!4{&0m|; zuU@C+_9gk>f9Z9;{w6LoYWQCmn zODO+gK>ph)$p0c=!__s{&XvZdJnT)ZQ&Z~{Y@M2!CyR;yGv@UVA^fLq#J_;~iWXjf z=|cqlnBqGhqV|s|zSD|^skEY?b?x#Y>ld~5kr7GeI}^8e^Id%9-j{jG>m z>f@hh<*roUhjF=*uIEw`Rg$QdM2#ex67F?#9$BrhZ_=XhFIIp4sp}y>n7(giY;IRe z?FG)ZWRkqqCq+KyI_iHcoS(aO_HL%+O`ZFZ@vg~%89)EJxZ+~SKQaDow_pDbmvsG} zY8CeH!&1Lz=QqKL1F6`#Ifb3}5&sI@|AC|9e$COJEmyfXlM;UT{ysSz@qKtA`Tma} z`t#=gB}bp8;^>VOjylp=;hyLy^M!?oe_;yp)t;uFDgI{u;o%DPHAzezugPGJDy9ZP zCb3@3_<6X+VvM;f#>3sY67vW91yeX#?1$Zg3BR`$Kk41?rSMHeg*2CrlFfv#b?q|a z3HI40B1{A0@M<1yzDJV0t*qIKasmBbg1_8lZg#Hz!(T2rFR zwOmsoYi{cBv?k~FIcCLja~On8gd5n-lp8sywuKQOr=(!nR6SH1*6uA=Q} zO--r1be(HgR=HG#nk&S~JK}PEoHEmF`rmETa=yRg@QI3!jSMmI_-$FqZ})gRH|RQd z;(ELG=>B$`O6SCwa|^M#+m}flluzF6oBVQ9de_TMsrLK+!{$?S>D{@6ZM|w>9o|XQ zCy83`BUne;Bx=2XU>)g_X!;~-y^>%Z8Iq`Tsxas;W$x^st**(4nwCKQ%3HzP{CWSZ z7k@rhB##BZAEd2bxR`F)Y_nZox1jxWCvP${U93nks7X9=Gs|!uW)#wSn0CQ=n3l|W zSWB|j@w5E^&Sp5g60A);$qY(gr&81(^T%A`Oh~rFYgscUnVw*dGQt8~u-V97Qi4hwk04t2<30q$z15y!0!C^`GiPe^al?$@_d i8BBV#_=OGTt4c`=(()~UF literal 0 HcmV?d00001 From dd51140f6c5137965e353f7de1dd820fd2c0e8b4 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 12:01:26 -0500 Subject: [PATCH 12/33] skip feather --- pandas/tests/io/test_feather.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pandas/tests/io/test_feather.py b/pandas/tests/io/test_feather.py index ee668d6890756..ef524f88333f5 100644 --- a/pandas/tests/io/test_feather.py +++ b/pandas/tests/io/test_feather.py @@ -4,6 +4,8 @@ import numpy as np import pytest +import pandas.util._test_decorators as td + import pandas as pd import pandas.util.testing as tm from pandas.util.testing import assert_frame_equal, ensure_clean @@ -48,6 +50,7 @@ def test_error(self): ]: self.check_error_on_write(obj, ValueError) + @td.skip_if_no("pyarrow", "0.15.0") # Sparse removal def test_basic(self): df = pd.DataFrame( @@ -74,6 +77,7 @@ def test_basic(self): assert df.dttz.dtype.tz.zone == "US/Eastern" self.check_round_trip(df) + @td.skip_if_no("pyarrow", "0.15.0") # Sparse removal def test_duplicate_columns(self): # https://github.com/wesm/feather/issues/53 @@ -86,6 +90,7 @@ def test_stringify_columns(self): df = pd.DataFrame(np.arange(12).reshape(4, 3)).copy() self.check_error_on_write(df, ValueError) + @td.skip_if_no("pyarrow", "0.15.0") # Sparse removal def test_read_columns(self): # GH 24025 df = pd.DataFrame( @@ -106,6 +111,7 @@ def test_unsupported_other(self): # Some versions raise ValueError, others raise ArrowInvalid. self.check_error_on_write(df, Exception) + @td.skip_if_no("pyarrow", "0.15.0") # Sparse removal def test_rw_nthreads(self): df = pd.DataFrame({"A": np.arange(100000)}) expected_warning = ( @@ -123,11 +129,13 @@ def test_rw_nthreads(self): # we have an extra FutureWarnings because of #GH23752 assert any(expected_warning in str(x) for x in w) + @td.skip_if_no("pyarrow", "0.15.0") # Sparse removal def test_rw_use_threads(self): df = pd.DataFrame({"A": np.arange(100000)}) self.check_round_trip(df, use_threads=True) self.check_round_trip(df, use_threads=False) + @td.skip_if_no("pyarrow", "0.15.0") # Sparse removal def test_write_with_index(self): df = pd.DataFrame({"A": [1, 2, 3]}) @@ -155,11 +163,13 @@ def test_write_with_index(self): df.columns = (pd.MultiIndex.from_tuples([("a", 1), ("a", 2), ("b", 1)]),) self.check_error_on_write(df, ValueError) + @td.skip_if_no("pyarrow", "0.15.0") # Sparse removal def test_path_pathlib(self): df = tm.makeDataFrame().reset_index() result = tm.round_trip_pathlib(df.to_feather, pd.read_feather) tm.assert_frame_equal(df, result) + @td.skip_if_no("pyarrow", "0.15.0") # Sparse removal def test_path_localpath(self): df = tm.makeDataFrame().reset_index() result = tm.round_trip_localpath(df.to_feather, pd.read_feather) From 129e89ecebc0bfe8a7d548c033bcda3a60e128c8 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 12:07:42 -0500 Subject: [PATCH 13/33] fixups --- pandas/tests/io/json/test_pandas.py | 16 ++++++++++ pandas/tests/series/test_combine_concat.py | 36 ++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/pandas/tests/io/json/test_pandas.py b/pandas/tests/io/json/test_pandas.py index 11df2b85086b6..7f861da6eb1f8 100644 --- a/pandas/tests/io/json/test_pandas.py +++ b/pandas/tests/io/json/test_pandas.py @@ -1296,6 +1296,22 @@ def test_datetime_tz(self): s_naive = Series(tz_naive) assert stz.to_json() == s_naive.to_json() + def test_sparse(self): + # GH4377 df.to_json segfaults with non-ndarray blocks + df = pd.DataFrame(np.random.randn(10, 4)) + df.loc[:8] = np.nan + + sdf = df.astype("Sparse") + expected = df.to_json() + assert expected == sdf.to_json() + + s = pd.Series(np.random.randn(10)) + s.loc[:8] = np.nan + ss = s.astype("Sparse") + + expected = s.to_json() + assert expected == ss.to_json() + def test_tz_is_utc(self): from pandas.io.json import dumps diff --git a/pandas/tests/series/test_combine_concat.py b/pandas/tests/series/test_combine_concat.py index f653f42eece7a..9efcc4a7392de 100644 --- a/pandas/tests/series/test_combine_concat.py +++ b/pandas/tests/series/test_combine_concat.py @@ -271,6 +271,42 @@ def test_concat_empty_series_dtypes(self): == "object" ) + # sparse + # TODO: move? + result = pd.concat( + [ + Series(dtype="float64").astype("Sparse"), + Series(dtype="float64").astype("Sparse"), + ] + ) + assert result.dtype == "Sparse[float64]" + + # GH 26705 - Assert .ftype is deprecated + with tm.assert_produces_warning(FutureWarning): + assert result.ftype == "float64:sparse" + + result = pd.concat( + [Series(dtype="float64").astype("Sparse"), Series(dtype="float64")] + ) + # TODO: release-note: concat sparse dtype + expected = pd.SparseDtype(np.float64) + assert result.dtype == expected + + # GH 26705 - Assert .ftype is deprecated + with tm.assert_produces_warning(FutureWarning): + assert result.ftype == "float64:sparse" + + result = pd.concat( + [Series(dtype="float64").astype("Sparse"), Series(dtype="object")] + ) + # TODO: release-note: concat sparse dtype + expected = pd.SparseDtype("object") + assert result.dtype == expected + + # GH 26705 - Assert .ftype is deprecated + with tm.assert_produces_warning(FutureWarning): + assert result.ftype == "object:sparse" + def test_combine_first_dt64(self): from pandas.core.tools.datetimes import to_datetime From 075bfd22814566082fc6cfa08fbc95f6dd049059 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 13:25:28 -0500 Subject: [PATCH 14/33] cleanups --- pandas/tests/arrays/sparse/test_array.py | 1 - pandas/tests/dtypes/test_common.py | 5 ----- pandas/tests/dtypes/test_dtypes.py | 3 +-- pandas/tests/frame/test_alter_axes.py | 2 -- pandas/tests/io/test_packers.py | 1 - pandas/tests/io/test_pickle.py | 2 -- pandas/tests/reshape/test_reshape.py | 1 - pandas/tests/series/test_api.py | 4 +--- 8 files changed, 2 insertions(+), 17 deletions(-) diff --git a/pandas/tests/arrays/sparse/test_array.py b/pandas/tests/arrays/sparse/test_array.py index c26aefa123f20..5d5ee565c7891 100644 --- a/pandas/tests/arrays/sparse/test_array.py +++ b/pandas/tests/arrays/sparse/test_array.py @@ -1112,7 +1112,6 @@ def test_npoints(self): assert arr.npoints == 1 -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") class TestAccessor: @pytest.mark.parametrize("attr", ["npoints", "density", "fill_value", "sp_values"]) def test_get_attributes(self, attr): diff --git a/pandas/tests/dtypes/test_common.py b/pandas/tests/dtypes/test_common.py index f5d31eb3eb4aa..b87a6bcce4a2a 100644 --- a/pandas/tests/dtypes/test_common.py +++ b/pandas/tests/dtypes/test_common.py @@ -23,8 +23,6 @@ ) import pandas.util.testing as tm -ignore_sparse_warning = pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") - # EA & Actual Dtypes def to_ea_dtypes(dtypes): @@ -178,7 +176,6 @@ def test_is_object(): @pytest.mark.parametrize( "check_scipy", [False, pytest.param(True, marks=td.skip_if_no_scipy)] ) -@ignore_sparse_warning def test_is_sparse(check_scipy): assert com.is_sparse(pd.SparseArray([1, 2, 3])) @@ -191,7 +188,6 @@ def test_is_sparse(check_scipy): @td.skip_if_no_scipy -@ignore_sparse_warning def test_is_scipy_sparse(): from scipy.sparse import bsr_matrix @@ -583,7 +579,6 @@ def test_is_bool_dtype(): @pytest.mark.parametrize( "check_scipy", [False, pytest.param(True, marks=td.skip_if_no_scipy)] ) -@ignore_sparse_warning def test_is_extension_type(check_scipy): assert not com.is_extension_type([1, 2, 3]) assert not com.is_extension_type(np.array([1, 2, 3])) diff --git a/pandas/tests/dtypes/test_dtypes.py b/pandas/tests/dtypes/test_dtypes.py index d3f0d7c43ee6b..3288c9c584565 100644 --- a/pandas/tests/dtypes/test_dtypes.py +++ b/pandas/tests/dtypes/test_dtypes.py @@ -960,9 +960,8 @@ def test_is_bool_dtype(dtype, expected): assert result is expected -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") def test_is_bool_dtype_sparse(): - result = is_bool_dtype(pd.SparseSeries([True, False])) + result = is_bool_dtype(pd.Series(pd.SparseArray([True, False]))) assert result is True diff --git a/pandas/tests/frame/test_alter_axes.py b/pandas/tests/frame/test_alter_axes.py index 00b59fd4dc087..017cbea7ec723 100644 --- a/pandas/tests/frame/test_alter_axes.py +++ b/pandas/tests/frame/test_alter_axes.py @@ -27,7 +27,6 @@ import pandas.util.testing as tm -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") class TestDataFrameAlterAxes: def test_set_index_directly(self, float_string_frame): df = float_string_frame @@ -1452,7 +1451,6 @@ def test_droplevel(self): tm.assert_frame_equal(result, expected) -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") class TestIntervalIndex: def test_setitem(self): diff --git a/pandas/tests/io/test_packers.py b/pandas/tests/io/test_packers.py index 95887bb69cdbc..0bafbab069dd4 100644 --- a/pandas/tests/io/test_packers.py +++ b/pandas/tests/io/test_packers.py @@ -835,7 +835,6 @@ def legacy_packer(request, datapath): return datapath(request.param) -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") @pytest.mark.filterwarnings("ignore:.*msgpack:FutureWarning") class TestMsgpack: """ diff --git a/pandas/tests/io/test_pickle.py b/pandas/tests/io/test_pickle.py index 59bd23727a33e..b5014021def5b 100644 --- a/pandas/tests/io/test_pickle.py +++ b/pandas/tests/io/test_pickle.py @@ -143,7 +143,6 @@ def legacy_pickle(request, datapath): # --------------------- # tests # --------------------- -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") def test_pickles(current_pickle_data, legacy_pickle): if not is_platform_little_endian(): pytest.skip("known failure on non-little endian") @@ -154,7 +153,6 @@ def test_pickles(current_pickle_data, legacy_pickle): compare(current_pickle_data, legacy_pickle, version) -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") def test_round_trip_current(current_pickle_data): def python_pickler(obj, path): with open(path, "wb") as fh: diff --git a/pandas/tests/reshape/test_reshape.py b/pandas/tests/reshape/test_reshape.py index 652c622a40921..9d08981d39894 100644 --- a/pandas/tests/reshape/test_reshape.py +++ b/pandas/tests/reshape/test_reshape.py @@ -13,7 +13,6 @@ from pandas.util.testing import assert_frame_equal -@pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") class TestGetDummies: @pytest.fixture def df(self): diff --git a/pandas/tests/series/test_api.py b/pandas/tests/series/test_api.py index 935d71db9b669..9aba17c076bc7 100644 --- a/pandas/tests/series/test_api.py +++ b/pandas/tests/series/test_api.py @@ -200,11 +200,9 @@ def test_constructor_dict_timedelta_index(self): ) self._assert_series_equal(result, expected) - @pytest.mark.filterwarnings("ignore:Sparse:FutureWarning") def test_from_array_deprecated(self): - # multiple FutureWarnings, so can't assert stacklevel - with tm.assert_produces_warning(FutureWarning, check_stacklevel=False): + with tm.assert_produces_warning(FutureWarning, check_stacklevel=True): self.series_klass.from_array([1, 2, 3]) def test_sparse_accessor_updates_on_inplace(self): From 2b58e53202204d20beff31c4b193ae8469aadb83 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 14:08:52 -0500 Subject: [PATCH 15/33] black --- pandas/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pandas/__init__.py b/pandas/__init__.py index 17706f6a8d16c..4770441e22fef 100644 --- a/pandas/__init__.py +++ b/pandas/__init__.py @@ -114,10 +114,7 @@ DataFrame, ) -from pandas.core.sparse.api import ( - SparseArray, - SparseDtype, -) +from pandas.core.sparse.api import SparseArray, SparseDtype from pandas.tseries.api import infer_freq from pandas.tseries import offsets From 413347fa40118435019e211de4543a2fa94bb6b2 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 14:10:03 -0500 Subject: [PATCH 16/33] to_sparse docs --- doc/redirects.csv | 2 -- doc/source/reference/frame.rst | 1 - doc/source/reference/series.rst | 1 - 3 files changed, 4 deletions(-) diff --git a/doc/redirects.csv b/doc/redirects.csv index a7886779c97d5..a1504f9175480 100644 --- a/doc/redirects.csv +++ b/doc/redirects.csv @@ -503,7 +503,6 @@ generated/pandas.DataFrame.to_parquet,../reference/api/pandas.DataFrame.to_parqu generated/pandas.DataFrame.to_period,../reference/api/pandas.DataFrame.to_period generated/pandas.DataFrame.to_pickle,../reference/api/pandas.DataFrame.to_pickle generated/pandas.DataFrame.to_records,../reference/api/pandas.DataFrame.to_records -generated/pandas.DataFrame.to_sparse,../reference/api/pandas.DataFrame.to_sparse generated/pandas.DataFrame.to_sql,../reference/api/pandas.DataFrame.to_sql generated/pandas.DataFrame.to_stata,../reference/api/pandas.DataFrame.to_stata generated/pandas.DataFrame.to_string,../reference/api/pandas.DataFrame.to_string @@ -1432,7 +1431,6 @@ generated/pandas.Series.to_msgpack,../reference/api/pandas.Series.to_msgpack generated/pandas.Series.to_numpy,../reference/api/pandas.Series.to_numpy generated/pandas.Series.to_period,../reference/api/pandas.Series.to_period generated/pandas.Series.to_pickle,../reference/api/pandas.Series.to_pickle -generated/pandas.Series.to_sparse,../reference/api/pandas.Series.to_sparse generated/pandas.Series.to_sql,../reference/api/pandas.Series.to_sql generated/pandas.Series.to_string,../reference/api/pandas.Series.to_string generated/pandas.Series.to_timestamp,../reference/api/pandas.Series.to_timestamp diff --git a/doc/source/reference/frame.rst b/doc/source/reference/frame.rst index ccbeb7a100b09..4982edeb7f85b 100644 --- a/doc/source/reference/frame.rst +++ b/doc/source/reference/frame.rst @@ -356,7 +356,6 @@ Serialization / IO / conversion DataFrame.to_msgpack DataFrame.to_gbq DataFrame.to_records - DataFrame.to_sparse DataFrame.to_dense DataFrame.to_string DataFrame.to_clipboard diff --git a/doc/source/reference/series.rst b/doc/source/reference/series.rst index bef6b10be3fff..5d825c8092efc 100644 --- a/doc/source/reference/series.rst +++ b/doc/source/reference/series.rst @@ -576,7 +576,6 @@ Serialization / IO / conversion Series.to_sql Series.to_msgpack Series.to_json - Series.to_sparse Series.to_dense Series.to_string Series.to_clipboard From d5828e347a8d15b321249feb1aa669a56a169237 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 14:11:37 -0500 Subject: [PATCH 17/33] doc note --- pandas/compat/pickle_compat.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pandas/compat/pickle_compat.py b/pandas/compat/pickle_compat.py index d08f2178bb1a2..87c8b92f4b454 100644 --- a/pandas/compat/pickle_compat.py +++ b/pandas/compat/pickle_compat.py @@ -56,6 +56,7 @@ def load_reduce(self): class _LoadSparseSeries: + # To load a SparseSeries as a Series[Sparse] def __new__(cls) -> Any: from pandas import Series @@ -63,6 +64,7 @@ def __new__(cls) -> Any: class _LoadSparseFrame: + # To load a SparseDataFrame as a DataFrame[Sparse] def __new__(cls) -> Any: from pandas import DataFrame From 047773ec594a08f7e273b357165b51a223de7d0f Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 14:12:31 -0500 Subject: [PATCH 18/33] rm sparse frame --- pandas/core/sparse/frame.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 pandas/core/sparse/frame.py diff --git a/pandas/core/sparse/frame.py b/pandas/core/sparse/frame.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 From 2d8d1951a5ffb614c7f4316fd679ce7407465aa2 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 14:12:48 -0500 Subject: [PATCH 19/33] rm sparse series --- pandas/core/sparse/series.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 pandas/core/sparse/series.py diff --git a/pandas/core/sparse/series.py b/pandas/core/sparse/series.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 From fa508c18af23780aa590bd7649e04d85ad4720e1 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 14:53:46 -0500 Subject: [PATCH 20/33] docs --- doc/source/user_guide/io.rst | 37 +++++++++++++++++++++++---------- doc/source/whatsnew/v0.16.0.rst | 6 ++---- doc/source/whatsnew/v0.18.1.rst | 6 ++---- doc/source/whatsnew/v0.19.0.rst | 6 ++---- doc/source/whatsnew/v0.20.0.rst | 5 ++--- 5 files changed, 34 insertions(+), 26 deletions(-) diff --git a/doc/source/user_guide/io.rst b/doc/source/user_guide/io.rst index f6b0c55d39f65..7b4652ff2e613 100644 --- a/doc/source/user_guide/io.rst +++ b/doc/source/user_guide/io.rst @@ -4658,26 +4658,41 @@ See the `Full Documentation `__. Write to a feather file. -.. ipython:: python - :okwarning: +.. TODO(Arrow 0.15): remove change these back to .. ipython blocks. - df.to_feather('example.feather') +.. code-block:: python -Read from a feather file. + >>> df.to_feather('example.feather') -.. ipython:: python - :okwarning: +Read from a feather file. - result = pd.read_feather('example.feather') - result +.. code-block:: python - # we preserve dtypes - result.dtypes + >>> result = pd.read_feather('example.feather') + >>> result + a b c d e f g h i + 0 a 1 3 4.0 True a 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000 + 1 b 2 4 5.0 False b 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001 + 2 c 3 5 6.0 True c 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002 + + >>> # we preserve dtypes + >>> result.dtypes + a object + b int64 + c uint8 + d float64 + e bool + f category + g datetime64[ns] + h datetime64[ns, US/Eastern] + i datetime64[ns] + dtype: object .. ipython:: python :suppress: - os.remove('example.feather') + if os.path.exists("example.feather"): + os.remove('example.feather') .. _io.parquet: diff --git a/doc/source/whatsnew/v0.16.0.rst b/doc/source/whatsnew/v0.16.0.rst index 42b3b9332ca98..fc638e35ed88b 100644 --- a/doc/source/whatsnew/v0.16.0.rst +++ b/doc/source/whatsnew/v0.16.0.rst @@ -91,8 +91,7 @@ Interaction with scipy.sparse Added :meth:`SparseSeries.to_coo` and :meth:`SparseSeries.from_coo` methods (:issue:`8048`) for converting to and from ``scipy.sparse.coo_matrix`` instances (see :ref:`here `). For example, given a SparseSeries with MultiIndex we can convert to a `scipy.sparse.coo_matrix` by specifying the row and column labels as index levels: -.. ipython:: python - :okwarning: +.. code-block:: python s = pd.Series([3.0, np.nan, 1.0, 3.0, np.nan, np.nan]) s.index = pd.MultiIndex.from_tuples([(1, 2, 'a', 0), @@ -121,8 +120,7 @@ Added :meth:`SparseSeries.to_coo` and :meth:`SparseSeries.from_coo` methods (:is The from_coo method is a convenience method for creating a ``SparseSeries`` from a ``scipy.sparse.coo_matrix``: -.. ipython:: python - :okwarning: +.. code-block:: python from scipy import sparse A = sparse.coo_matrix(([3.0, 1.0, 2.0], ([1, 0, 0], [0, 2, 3])), diff --git a/doc/source/whatsnew/v0.18.1.rst b/doc/source/whatsnew/v0.18.1.rst index 7e06e5050c5f0..f786ce513f6fe 100644 --- a/doc/source/whatsnew/v0.18.1.rst +++ b/doc/source/whatsnew/v0.18.1.rst @@ -393,8 +393,7 @@ used in the ``pandas`` implementation (:issue:`12644`, :issue:`12638`, :issue:`1 An example of this signature augmentation is illustrated below: -.. ipython:: python - :okwarning: +.. code-block:: python sp = pd.SparseDataFrame([1, 2, 3]) sp @@ -409,8 +408,7 @@ Previous behaviour: New behaviour: -.. ipython:: python - :okwarning: +.. code-block:: python np.cumsum(sp, axis=0) diff --git a/doc/source/whatsnew/v0.19.0.rst b/doc/source/whatsnew/v0.19.0.rst index 1dad8769a6b39..61a65415f6b57 100644 --- a/doc/source/whatsnew/v0.19.0.rst +++ b/doc/source/whatsnew/v0.19.0.rst @@ -1235,8 +1235,7 @@ Operators now preserve dtypes - Sparse data structure now can preserve ``dtype`` after arithmetic ops (:issue:`13848`) -.. ipython:: python - :okwarning: +.. code-block:: python s = pd.SparseSeries([0, 2, 0, 1], fill_value=0, dtype=np.int64) s.dtype @@ -1245,8 +1244,7 @@ Operators now preserve dtypes - Sparse data structure now support ``astype`` to convert internal ``dtype`` (:issue:`13900`) -.. ipython:: python - :okwarning: +.. code-block:: python s = pd.SparseSeries([1., 0., 2., 0.], fill_value=0) s diff --git a/doc/source/whatsnew/v0.20.0.rst b/doc/source/whatsnew/v0.20.0.rst index 62604dd3edd2d..c7278d5a47ba6 100644 --- a/doc/source/whatsnew/v0.20.0.rst +++ b/doc/source/whatsnew/v0.20.0.rst @@ -338,8 +338,7 @@ See the :ref:`documentation ` for more information. (:issue: All sparse formats are supported, but matrices that are not in :mod:`COOrdinate ` format will be converted, copying data as needed. -.. ipython:: python - :okwarning: +.. code-block:: python from scipy.sparse import csr_matrix arr = np.random.random(size=(1000, 5)) @@ -351,7 +350,7 @@ All sparse formats are supported, but matrices that are not in :mod:`COOrdinate To convert a ``SparseDataFrame`` back to sparse SciPy matrix in COO format, you can use: -.. ipython:: python +.. code-block:: python sdf.to_coo() From 9b613708485bdb4828f53c6804aba032990de9e3 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 16:13:31 -0500 Subject: [PATCH 21/33] doc --- doc/source/whatsnew/v0.25.0.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index fe1e2d7826d62..35f84d701a2ea 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -353,7 +353,7 @@ Concatenating sparse values When passed DataFrames whose values are sparse, :func:`concat` will now return a :class:`Series` or :class:`DataFrame` with sparse values, rather than a :class:`SparseDataFrame` (:issue:`25702`). -.. ipython:: python +.. code-block:: python df = pd.DataFrame({"A": pd.SparseArray([0, 1])}) @@ -366,7 +366,7 @@ When passed DataFrames whose values are sparse, :func:`concat` will now return a *New behavior*: -.. ipython:: python +.. code-block:: python type(pd.concat([df, df])) From 0c530ae1901fd350b81dc705bf77d80580844416 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 16:15:12 -0500 Subject: [PATCH 22/33] remove new pickle --- .../0.20.3/0.20.3_x86_64_darwin_3.5.6.pickle | Bin 127523 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 pandas/tests/io/data/legacy_pickle/0.20.3/0.20.3_x86_64_darwin_3.5.6.pickle diff --git a/pandas/tests/io/data/legacy_pickle/0.20.3/0.20.3_x86_64_darwin_3.5.6.pickle b/pandas/tests/io/data/legacy_pickle/0.20.3/0.20.3_x86_64_darwin_3.5.6.pickle deleted file mode 100644 index 05cefcfd7974b5abdd67220647ecb2abec317fea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 127523 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 O1fC~ Date: Fri, 13 Sep 2019 18:43:03 -0500 Subject: [PATCH 23/33] Update v0.25.0.rst --- doc/source/whatsnew/v0.25.0.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 35f84d701a2ea..c94ef1a53f2e4 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -353,7 +353,7 @@ Concatenating sparse values When passed DataFrames whose values are sparse, :func:`concat` will now return a :class:`Series` or :class:`DataFrame` with sparse values, rather than a :class:`SparseDataFrame` (:issue:`25702`). -.. code-block:: python +.. ipython:: python df = pd.DataFrame({"A": pd.SparseArray([0, 1])}) @@ -366,7 +366,7 @@ When passed DataFrames whose values are sparse, :func:`concat` will now return a *New behavior*: -.. code-block:: python +.. ipython:: python type(pd.concat([df, df])) @@ -902,7 +902,7 @@ by a ``Series`` or ``DataFrame`` with sparse values. **Previous way** -.. ipython:: python +.. code-block:: python :okwarning: df = pd.SparseDataFrame({"A": [0, 0, 1, 2]}) From 58b848ad34ce8d06d202e4c558e7fe14a959c039 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 13 Sep 2019 18:44:06 -0500 Subject: [PATCH 24/33] Update v0.25.0.rst --- doc/source/whatsnew/v0.25.0.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index c94ef1a53f2e4..2eca1c6c7636c 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -903,7 +903,6 @@ by a ``Series`` or ``DataFrame`` with sparse values. **Previous way** .. code-block:: python - :okwarning: df = pd.SparseDataFrame({"A": [0, 0, 1, 2]}) df.dtypes From f1afc8f2c266e33fa0444775bb20b71276819c9d Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Mon, 16 Sep 2019 14:58:42 -0500 Subject: [PATCH 25/33] added new legacy pickle files --- .../0.20.3/0.20.3_x86_64_darwin_3.5.6.pickle | Bin 0 -> 127244 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pandas/tests/io/data/legacy_pickle/0.20.3/0.20.3_x86_64_darwin_3.5.6.pickle diff --git a/pandas/tests/io/data/legacy_pickle/0.20.3/0.20.3_x86_64_darwin_3.5.6.pickle b/pandas/tests/io/data/legacy_pickle/0.20.3/0.20.3_x86_64_darwin_3.5.6.pickle new file mode 100644 index 0000000000000000000000000000000000000000..88bb6989f5b088e55cdfde2423eb9f0b6d393bb7 GIT binary patch literal 127244 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 G`2PT0b>sN} literal 0 HcmV?d00001 From 008931abd6528b1ca507cc821c4a9e679a730500 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Tue, 17 Sep 2019 08:53:18 -0500 Subject: [PATCH 26/33] shim --- pandas/tests/api/test_api.py | 2 +- pandas/tests/io/test_feather.py | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/pandas/tests/api/test_api.py b/pandas/tests/api/test_api.py index 0958b9472d591..2f24bbd6f0c85 100644 --- a/pandas/tests/api/test_api.py +++ b/pandas/tests/api/test_api.py @@ -88,7 +88,7 @@ class TestPDApi(Base): "NamedAgg", ] if not compat.PY37: - classes.append("Panel") + classes.extend(["Panel", "SparseSeries", "SparseDataFrame"]) # these are already deprecated; awaiting removal deprecated_classes = [] diff --git a/pandas/tests/io/test_feather.py b/pandas/tests/io/test_feather.py index ef524f88333f5..ea69245924b0c 100644 --- a/pandas/tests/io/test_feather.py +++ b/pandas/tests/io/test_feather.py @@ -4,8 +4,6 @@ import numpy as np import pytest -import pandas.util._test_decorators as td - import pandas as pd import pandas.util.testing as tm from pandas.util.testing import assert_frame_equal, ensure_clean @@ -16,8 +14,10 @@ pyarrow_version = LooseVersion(pyarrow.__version__) +filter_sparse = pytest.mark.filterwarnings("ignore:The Sparse") +@filter_sparse @pytest.mark.single class TestFeather: def check_error_on_write(self, df, exc): @@ -50,7 +50,6 @@ def test_error(self): ]: self.check_error_on_write(obj, ValueError) - @td.skip_if_no("pyarrow", "0.15.0") # Sparse removal def test_basic(self): df = pd.DataFrame( @@ -77,7 +76,6 @@ def test_basic(self): assert df.dttz.dtype.tz.zone == "US/Eastern" self.check_round_trip(df) - @td.skip_if_no("pyarrow", "0.15.0") # Sparse removal def test_duplicate_columns(self): # https://github.com/wesm/feather/issues/53 @@ -90,7 +88,6 @@ def test_stringify_columns(self): df = pd.DataFrame(np.arange(12).reshape(4, 3)).copy() self.check_error_on_write(df, ValueError) - @td.skip_if_no("pyarrow", "0.15.0") # Sparse removal def test_read_columns(self): # GH 24025 df = pd.DataFrame( @@ -111,7 +108,6 @@ def test_unsupported_other(self): # Some versions raise ValueError, others raise ArrowInvalid. self.check_error_on_write(df, Exception) - @td.skip_if_no("pyarrow", "0.15.0") # Sparse removal def test_rw_nthreads(self): df = pd.DataFrame({"A": np.arange(100000)}) expected_warning = ( @@ -129,13 +125,11 @@ def test_rw_nthreads(self): # we have an extra FutureWarnings because of #GH23752 assert any(expected_warning in str(x) for x in w) - @td.skip_if_no("pyarrow", "0.15.0") # Sparse removal def test_rw_use_threads(self): df = pd.DataFrame({"A": np.arange(100000)}) self.check_round_trip(df, use_threads=True) self.check_round_trip(df, use_threads=False) - @td.skip_if_no("pyarrow", "0.15.0") # Sparse removal def test_write_with_index(self): df = pd.DataFrame({"A": [1, 2, 3]}) @@ -163,13 +157,11 @@ def test_write_with_index(self): df.columns = (pd.MultiIndex.from_tuples([("a", 1), ("a", 2), ("b", 1)]),) self.check_error_on_write(df, ValueError) - @td.skip_if_no("pyarrow", "0.15.0") # Sparse removal def test_path_pathlib(self): df = tm.makeDataFrame().reset_index() result = tm.round_trip_pathlib(df.to_feather, pd.read_feather) tm.assert_frame_equal(df, result) - @td.skip_if_no("pyarrow", "0.15.0") # Sparse removal def test_path_localpath(self): df = tm.makeDataFrame().reset_index() result = tm.round_trip_localpath(df.to_feather, pd.read_feather) From 04bf466640f8aa2d34e1f4aa0e79ddabe64f0a82 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Tue, 17 Sep 2019 08:53:54 -0500 Subject: [PATCH 27/33] shim --- doc/source/user_guide/io.rst | 4 +--- pandas/__init__.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/doc/source/user_guide/io.rst b/doc/source/user_guide/io.rst index 5570bd44bb925..94ec6ce72cd71 100644 --- a/doc/source/user_guide/io.rst +++ b/doc/source/user_guide/io.rst @@ -4658,9 +4658,7 @@ See the `Full Documentation `__. Write to a feather file. -.. TODO(Arrow 0.15): remove change these back to .. ipython blocks. - -.. code-block:: python +.. ipython:: python >>> df.to_feather('example.feather') diff --git a/pandas/__init__.py b/pandas/__init__.py index 4770441e22fef..93f25de8fd659 100644 --- a/pandas/__init__.py +++ b/pandas/__init__.py @@ -191,8 +191,9 @@ if pandas.compat.PY37: def __getattr__(name): + import warnings + if name == "Panel": - import warnings warnings.warn( "The Panel class is removed from pandas. Accessing it " @@ -206,6 +207,13 @@ class Panel: pass return Panel + elif name in {"SparseSeries", "SparseDataFrame"}: + warnings.warn("The {} class is removed from pandas. Accessing it from " + "the top-level namespace will also be removed in the next " + "version".format(name), FutureWarning, stacklevel=2) + + return type(name, (), {}) + raise AttributeError("module 'pandas' has no attribute '{}'".format(name)) @@ -214,6 +222,12 @@ class Panel: class Panel: pass + class SparseDataFrame: + pass + + class SparseSeries: + pass + # module level doc-string __doc__ = """ From a4a21aef98d4bfd0a2fead65d04cc34c397283c5 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Tue, 17 Sep 2019 08:56:47 -0500 Subject: [PATCH 28/33] revert io changes --- doc/source/user_guide/io.rst | 41 ++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/doc/source/user_guide/io.rst b/doc/source/user_guide/io.rst index 94ec6ce72cd71..2c8f66dd99e72 100644 --- a/doc/source/user_guide/io.rst +++ b/doc/source/user_guide/io.rst @@ -4641,6 +4641,14 @@ Several caveats. See the `Full Documentation `__. +.. ipython:: python + :suppress: + + import warnings + # This can be removed once building with pyarrow >=0.15.0 + warnings.filterwarnings("ignore", "The Sparse", FutureWarning) + + .. ipython:: python df = pd.DataFrame({'a': list('abc'), @@ -4659,38 +4667,25 @@ See the `Full Documentation `__. Write to a feather file. .. ipython:: python + :okwarning: - >>> df.to_feather('example.feather') + df.to_feather('example.feather') Read from a feather file. -.. code-block:: python +.. ipython:: python + :okwarning: - >>> result = pd.read_feather('example.feather') - >>> result - a b c d e f g h i - 0 a 1 3 4.0 True a 2013-01-01 2013-01-01 00:00:00-05:00 2013-01-01 00:00:00.000000000 - 1 b 2 4 5.0 False b 2013-01-02 2013-01-02 00:00:00-05:00 2013-01-01 00:00:00.000000001 - 2 c 3 5 6.0 True c 2013-01-03 2013-01-03 00:00:00-05:00 2013-01-01 00:00:00.000000002 - - >>> # we preserve dtypes - >>> result.dtypes - a object - b int64 - c uint8 - d float64 - e bool - f category - g datetime64[ns] - h datetime64[ns, US/Eastern] - i datetime64[ns] - dtype: object + result = pd.read_feather('example.feather') + result + + # we preserve dtypes + result.dtypes .. ipython:: python :suppress: - if os.path.exists("example.feather"): - os.remove('example.feather') + os.remove('example.feather') .. _io.parquet: From a8b0d65154b76fcb0b62025d87d8ead9fcdde723 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Tue, 17 Sep 2019 09:14:03 -0500 Subject: [PATCH 29/33] warning for sparse --- pandas/compat/pickle_compat.py | 29 ++++++++++++++++-- .../io/data/sparseframe-0.20.3.pickle.gz | Bin 0 -> 625 bytes .../io/data/sparseseries-0.20.3.pickle.gz | Bin 0 -> 521 bytes pandas/tests/io/test_pickle.py | 26 ++++++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 pandas/tests/io/data/sparseframe-0.20.3.pickle.gz create mode 100644 pandas/tests/io/data/sparseseries-0.20.3.pickle.gz diff --git a/pandas/compat/pickle_compat.py b/pandas/compat/pickle_compat.py index 87c8b92f4b454..c88d08ec37fcd 100644 --- a/pandas/compat/pickle_compat.py +++ b/pandas/compat/pickle_compat.py @@ -5,10 +5,14 @@ import copy import pickle as pkl import sys -from typing import Any +from typing import TYPE_CHECKING +import warnings from pandas import Index +if TYPE_CHECKING: + from pandas._typing import FrameOrSeries + def load_reduce(self): stack = self.stack @@ -55,19 +59,38 @@ def load_reduce(self): raise +_sparse_msg = """\ + +Loading a saved '{cls}' as a {new} with sparse values. +'{cls}' is now removed. You should re-save this dataset in its new format. +""" + + class _LoadSparseSeries: # To load a SparseSeries as a Series[Sparse] - def __new__(cls) -> Any: + def __new__(cls) -> FrameOrSeries: from pandas import Series + warnings.warn( + _sparse_msg.format(cls="SparseSeries", new="Series"), + FutureWarning, + stacklevel=6, + ) + return Series() class _LoadSparseFrame: # To load a SparseDataFrame as a DataFrame[Sparse] - def __new__(cls) -> Any: + def __new__(cls) -> FrameOrSeries: from pandas import DataFrame + warnings.warn( + _sparse_msg.format(cls="SparseDataFrame", new="DataFrame"), + FutureWarning, + stacklevel=6, + ) + return DataFrame() diff --git a/pandas/tests/io/data/sparseframe-0.20.3.pickle.gz b/pandas/tests/io/data/sparseframe-0.20.3.pickle.gz new file mode 100644 index 0000000000000000000000000000000000000000..f4ff0dbaa1ff92bce1b6be92058373060b67c571 GIT binary patch literal 625 zcmV-%0*?J3iwFoa=zv`U|8sC*a&u*7a$#*{Uob8*FfKDLaA{*}Y-IqQRNre8K@h%6 z@6!6Slu(F>AP5qb9v1rGL!q=Oiam_9KFhL~+jNg!E@5|14OrBNR)htGjDLy$oNqe2 zx6x~i2yP(z<7VdjzMYwUAAJ25cy{Ram=-COQGX;@B*#=p77YX)GSI6p>~^0j`q-XK z;ob*0g^pe(vP{%u4Ek#2LY&JrlCl9Ub45i zSuJV*E%bW5gC98TU%aCr`DC>CyP*9&V7S*oVXmuJo}(9E(>E5$6tp-tI*-g;*A_}e zg+f#vNHrQm9Po{vSunocLP_d?^h0dDT-Mm8sHdrq2Xq;+rm zRVjH5H!r!_m6!(#p1k|+>aFD{kvvPJb$w(&&_pp{NQiY5C4yr&EpzxVcPlnV!yjsT>L&YaW`fK z^>2p(Y!QYcJ#{dSP=Y(y}Oc>8Fp5I z%TVT7pV@LUU%ACcc}{HnTCoJgJ~Ed~GgmK6nM6MU LLm3IRz61aOFitCK literal 0 HcmV?d00001 diff --git a/pandas/tests/io/data/sparseseries-0.20.3.pickle.gz b/pandas/tests/io/data/sparseseries-0.20.3.pickle.gz new file mode 100644 index 0000000000000000000000000000000000000000..b299e7d85808e540bff2ea5a9d688a677677289f GIT binary patch literal 521 zcmV+k0`~nMiwFoU=zv`U|8sC*a&u*KWpZg{b6+qnGB7SPE^ujMYiwlzZBk8d)G!d; zQ17+^Dj}++%cVj>NZSJum0HAs9%v5;B68@SE6dKBbwt)1`2*NPRXDT=$+r&wl^cwm z1Z_(lDU+GJ@w|C6-^j0nwO%~_an00}nY37Fk(!!mgGT6581VNe(VsN#73_Y8GZ_1I zY89+EStX9DMe*qa zQ*0)5Q*3++esn!)D!vMna%Kcpqe^_C??Mdm*E{KunyFcUo&?)LLlb>tnYNJiJTZ1r zLqB2E*0@ZDw9Dye^?)soza>vuZ LQ#LY-Kmz~(w%YyQ literal 0 HcmV?d00001 diff --git a/pandas/tests/io/test_pickle.py b/pandas/tests/io/test_pickle.py index b5014021def5b..4124c9aff9d34 100644 --- a/pandas/tests/io/test_pickle.py +++ b/pandas/tests/io/test_pickle.py @@ -228,6 +228,32 @@ def test_pickle_path_localpath(): tm.assert_frame_equal(df, result) +def test_legacy_sparse_warning(datapath): + """ + + Generated with + + >>> df = pd.DataFrame({"A": [1, 2, 3, 4], "B": [0, 0, 1, 1]}).to_sparse() + >>> df.to_pickle("pandas/tests/io/data/sparseframe-0.20.3.pickle.gz", + ... compression="gzip") + + >>> s = df['B'] + >>> s.to_pickle("pandas/tests/io/data/sparseseries-0.20.3.pickle.gz", + ... compression="gzip") + """ + with tm.assert_produces_warning(FutureWarning): + simplefilter("ignore", DeprecationWarning) # from boto + pd.read_pickle( + datapath("io", "data", "sparseseries-0.20.3.pickle.gz"), compression="gzip" + ) + + with tm.assert_produces_warning(FutureWarning): + simplefilter("ignore", DeprecationWarning) # from boto + pd.read_pickle( + datapath("io", "data", "sparseframe-0.20.3.pickle.gz"), compression="gzip" + ) + + # --------------------- # test pickle compression # --------------------- From 77b7da321b0e5582988c8cd013f0deac8b3b818d Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Tue, 17 Sep 2019 11:05:27 -0500 Subject: [PATCH 30/33] Fixup typing --- pandas/compat/pickle_compat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/compat/pickle_compat.py b/pandas/compat/pickle_compat.py index c88d08ec37fcd..4155cc588db9b 100644 --- a/pandas/compat/pickle_compat.py +++ b/pandas/compat/pickle_compat.py @@ -68,7 +68,7 @@ def load_reduce(self): class _LoadSparseSeries: # To load a SparseSeries as a Series[Sparse] - def __new__(cls) -> FrameOrSeries: + def __new__(cls) -> "FrameOrSeries": from pandas import Series warnings.warn( @@ -82,7 +82,7 @@ def __new__(cls) -> FrameOrSeries: class _LoadSparseFrame: # To load a SparseDataFrame as a DataFrame[Sparse] - def __new__(cls) -> FrameOrSeries: + def __new__(cls) -> "FrameOrSeries": from pandas import DataFrame warnings.warn( From d265ba9b8fa1642cba6cf8c070169dfbe730b47c Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Tue, 17 Sep 2019 12:09:57 -0500 Subject: [PATCH 31/33] format --- pandas/__init__.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pandas/__init__.py b/pandas/__init__.py index 93f25de8fd659..59ecc7f609ae9 100644 --- a/pandas/__init__.py +++ b/pandas/__init__.py @@ -208,9 +208,13 @@ class Panel: return Panel elif name in {"SparseSeries", "SparseDataFrame"}: - warnings.warn("The {} class is removed from pandas. Accessing it from " - "the top-level namespace will also be removed in the next " - "version".format(name), FutureWarning, stacklevel=2) + warnings.warn( + "The {} class is removed from pandas. Accessing it from " + "the top-level namespace will also be removed in the next " + "version".format(name), + FutureWarning, + stacklevel=2, + ) return type(name, (), {}) From c2a951408885c7f5930b085570b9bbf43b6692b8 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Tue, 17 Sep 2019 13:28:58 -0500 Subject: [PATCH 32/33] fixup typing --- pandas/compat/pickle_compat.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/compat/pickle_compat.py b/pandas/compat/pickle_compat.py index 4155cc588db9b..b3c7b8a7c8b9f 100644 --- a/pandas/compat/pickle_compat.py +++ b/pandas/compat/pickle_compat.py @@ -11,7 +11,7 @@ from pandas import Index if TYPE_CHECKING: - from pandas._typing import FrameOrSeries + from pandas import Series, DataFrame def load_reduce(self): @@ -68,7 +68,7 @@ def load_reduce(self): class _LoadSparseSeries: # To load a SparseSeries as a Series[Sparse] - def __new__(cls) -> "FrameOrSeries": + def __new__(cls) -> "Series": from pandas import Series warnings.warn( @@ -82,7 +82,7 @@ def __new__(cls) -> "FrameOrSeries": class _LoadSparseFrame: # To load a SparseDataFrame as a DataFrame[Sparse] - def __new__(cls) -> "FrameOrSeries": + def __new__(cls) -> "DataFrame": from pandas import DataFrame warnings.warn( From 0c02b2a28cfdfa8a8f47ce0919f262fd43d13e63 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Tue, 17 Sep 2019 13:55:52 -0500 Subject: [PATCH 33/33] 0.24.0 todo --- pandas/tests/io/generate_legacy_storage_files.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/tests/io/generate_legacy_storage_files.py b/pandas/tests/io/generate_legacy_storage_files.py index d0740b8a6b0f0..e63644a44a81f 100755 --- a/pandas/tests/io/generate_legacy_storage_files.py +++ b/pandas/tests/io/generate_legacy_storage_files.py @@ -85,6 +85,7 @@ ) try: + # TODO: remove try/except when 0.24.0 is the legacy version. from pandas.arrays import SparseArray except ImportError: from pandas.core.sparse.api import SparseArray