Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allows for merging of SparseDataFrames, and fixes __array__ interface #19488

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5b48613
Hrm I'm trying here but nothing seems to work
hexgnu Jan 26, 2018
555fb91
First pass at fixing issues with SparseDataFrame merging
hexgnu Feb 1, 2018
89677b0
Fix linting errors
hexgnu Feb 2, 2018
97bd19b
One more linting error
hexgnu Feb 2, 2018
b04ac64
Fixing more test failures
hexgnu Feb 2, 2018
d24e464
Add more tests and fix some existing ones
hexgnu Feb 2, 2018
a796280
Only allow for sparse merging if everything is sparse
hexgnu Feb 2, 2018
be09289
Some more tests
hexgnu Feb 2, 2018
1aef901
Merge remote-tracking branch 'upstream/master' into dense_array_sparse
hexgnu Feb 5, 2018
42680ba
Merge branch 'dense_array_sparse' into fix_for_merging_sparse_frames
hexgnu Feb 5, 2018
2084ed3
Fix problem with __array__ not showing dense values
hexgnu Feb 5, 2018
77c41b7
Fix some linting errors
hexgnu Feb 5, 2018
25fd08a
I think I fixed it
hexgnu Feb 5, 2018
cd583f7
Don't assume that the dtype is int64
hexgnu Feb 5, 2018
45e7cd3
Get rid of WTF from code
hexgnu Feb 5, 2018
6522d6b
Missed one point where assumed int64
hexgnu Feb 5, 2018
029d37b
Typo fix should be len(values.shape)
hexgnu Feb 5, 2018
730c152
This will fix windows bug on appveyor
hexgnu Feb 6, 2018
171f5dd
Linting error
hexgnu Feb 6, 2018
bde2588
Add whatsnew entry for everything
hexgnu Feb 6, 2018
bfe3065
Add docstring to is_sparse_join_units
hexgnu Feb 6, 2018
d13daa7
WIP for merging sparse frames
hexgnu Feb 7, 2018
3ddba8b
Merge remote-tracking branch 'upstream/master' into fix_for_merging_s…
hexgnu Feb 7, 2018
4fa7dec
Allow for indexes to where on SparseArray
hexgnu Feb 12, 2018
353171b
Rely on ABCSparseArray over SparseArray
hexgnu Feb 12, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion doc/source/whatsnew/v0.23.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ Other Enhancements

- ``IntervalIndex.astype`` now supports conversions between subtypes when passed an ``IntervalDtype`` (:issue:`19197`)
- :class:`IntervalIndex` and its associated constructor methods (``from_arrays``, ``from_breaks``, ``from_tuples``) have gained a ``dtype`` parameter (:issue:`19262`)
- :func:`pandas.merge` now supports merging of :class:`SparseDataFrame` (:issue:`13665`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarify: merging sparse to sparse? Sparse and dense?


.. _whatsnew_0230.api_breaking:

Expand Down Expand Up @@ -555,7 +556,7 @@ Sparse

- Bug in which creating a ``SparseDataFrame`` from a dense ``Series`` or an unsupported type raised an uncontrolled exception (:issue:`19374`)
- Bug in :class:`SparseDataFrame.to_csv` causing exception (:issue:`19384`)
-
- Bug in :class:`SparseSeries.__array__` returning only non-faills (:issue:`13665`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in "faills"?


Reshaping
^^^^^^^^^
Expand Down Expand Up @@ -591,3 +592,4 @@ Other
^^^^^

- Improved error message when attempting to use a Python keyword as an identifier in a ``numexpr`` backed query (:issue:`18221`)
- Improved algorithms.take_1d handling of ``SparseArray`` (:issue:`19506`)
5 changes: 5 additions & 0 deletions pandas/core/algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,11 @@ def take_nd(arr, indexer, axis=0, out=None, fill_value=np.nan, mask_info=None,
undefined if allow_fill == False and -1 is present in indexer.
"""

if is_sparse(arr):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So something that is a bit of a hotspot IMHO is that inside of algos it iterates through the memview and could cause some weird security / segfault issues. This fills that hole but really should fix that at a lower level.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this seems sub-optimal. We shouldn't have to densify just to slice, right?

Can you take a look at SparseAray.take to see if any of that logic can be refactored / reused?

And since you're touching performance-related code, could you run the sparse-related ASVs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you for pointing this out. exactly what I was looking for. Will run the ASVs

return take_nd(arr.get_values(), indexer, axis=axis, out=out,
fill_value=fill_value, mask_info=mask_info,
allow_fill=allow_fill)

# dispatch to internal type takes
if is_categorical(arr):
return arr.take_nd(indexer, fill_value=fill_value,
Expand Down
34 changes: 33 additions & 1 deletion pandas/core/internals.py
Original file line number Diff line number Diff line change
Expand Up @@ -3089,6 +3089,7 @@ def make_block(values, placement, klass=None, ndim=None, dtype=None,
# GH#19265 pyarrow is passing this
warnings.warn("fastpath argument is deprecated, will be removed "
"in a future release.", DeprecationWarning)

if klass is None:
dtype = dtype or values.dtype
klass = get_block_type(values, dtype)
Expand Down Expand Up @@ -5304,6 +5305,22 @@ def concatenate_block_managers(mgrs_indexers, axes, concat_axis, copy):
elif is_uniform_join_units(join_units):
b = join_units[0].block.concat_same_type(
[ju.block for ju in join_units], placement=placement)
elif is_sparse_join_units(join_units):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you ever go down the initial if with sparse arrays?

values = concatenate_join_units(join_units, concat_axis, copy=copy)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a mess

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, yes it is. I'll work on cleaning this up tomorrow since there's too many branches in this code.


if len(values.shape) == 2:
values = values[0]
else:
assert len(values.shape) == 1

block = join_units[0].block

if block:
fill_value = block.fill_value
else:
fill_value = np.nan
array = SparseArray(values, fill_value=fill_value)
b = make_block(array, klass=SparseBlock, placement=placement)
else:
b = make_block(
concatenate_join_units(join_units, concat_axis, copy=copy),
Expand All @@ -5313,6 +5330,18 @@ def concatenate_block_managers(mgrs_indexers, axes, concat_axis, copy):
return BlockManager(blocks, axes)


def is_sparse_join_units(join_units):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring would be nice, at least noting that this is true if any blocks are sparse.

"""
Check if all of the join units are sparse. This leads to building
SparseArray over dense array representations so that we can merge
SparseSeries / SparseDataFrame

This is very similar to how pandas.concat works for conatting two
SparseDataFrame / SparseSeries
"""
return all(type(ju.block) is SparseBlock for ju in join_units)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very similar (and maybe could be DRYed up) like pd.concat([sparse, sparse])



def is_uniform_join_units(join_units):
"""
Check if the join units consist of blocks of uniform type that can
Expand Down Expand Up @@ -5686,7 +5715,10 @@ def is_na(self):
def get_reindexed_values(self, empty_dtype, upcasted_na):
if upcasted_na is None:
# No upcasting is necessary
fill_value = self.block.fill_value

# You would think that you want self.block.fill_value here
# But in reality that will fill with a bunch of wrong values
fill_value = np.nan
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was surprising to me. If you don't pass in np.nan what ends up happening is that if you merge two sparse frames it will concat with a bunch of fill_value's which is most definitely not what I would expect as a user.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea this confused me a bunch.

evens = pd.DataFrame({'A': range(0, 20, 2), 'B': range(10)})
threes = pd.DataFrame({'A': range(0, 30, 3), 'B': range(10)})
threes.merge(evens, how="left", on="A")

Yields

    A  B_x  B_y
0   0    0  0.0
1   3    1  NaN
2   6    2  3.0
3   9    3  NaN
4  12    4  6.0
5  15    5  NaN
6  18    6  9.0
7  21    7  NaN
8  24    8  NaN
9  27    9  NaN

As you'd expect for a dense dataframe

but if you sparsify it with a fill_value other than np.nan / None you get weird results

evens = pd.DataFrame({'A': range(0, 20, 2), 'B': range(10)}).to_sparse(fill_value=8675309)
threes = pd.DataFrame({'A': range(0, 30, 3), 'B': range(10)}).to_sparse(fill_value=90210)
threes.merge(evens, how="left", on="A")

Yields

    A  B_x      B_y
0   0    0        0
1   3    1  8675309
2   6    2        3
3   9    3  8675309
4  12    4        6
5  15    5  8675309
6  18    6        9
7  21    7  8675309
8  24    8  8675309
9  27    9  8675309

Is that expected behavior?

values = self.block.get_values()
else:
fill_value = upcasted_na
Expand Down
9 changes: 7 additions & 2 deletions pandas/core/reshape/merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
concatenate_block_managers)
from pandas.util._decorators import Appender, Substitution

from pandas.core.sparse.array import SparseArray

from pandas.core.sorting import is_int64_overflow_possible
import pandas.core.algorithms as algos
import pandas.core.sorting as sorting
Expand Down Expand Up @@ -665,7 +667,6 @@ def _maybe_restore_index_levels(self, result):
result.set_index(names_to_restore, inplace=True)

def _maybe_add_join_keys(self, result, left_indexer, right_indexer):

left_has_missing = None
right_has_missing = None

Expand Down Expand Up @@ -731,7 +732,11 @@ def _maybe_add_join_keys(self, result, left_indexer, right_indexer):
if mask.all():
key_col = rvals
else:
key_col = Index(lvals).where(~mask, rvals)
# Might need to be IntIndex not Index
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't do this, use _values

if isinstance(lvals, SparseArray):
key_col = Index(lvals.get_values()).where(~mask, rvals)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this has memory or performance issues. But this is the best solution I could come to with this. The other solution would be to look at using lvals.sp_index and implement a where on it that works.

One thing I have noticed is that IntIndex doesn't act quite like Index which makes for doing these masks tricky in sparse land.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be nice to avoid get_values if possible.

else:
key_col = Index(lvals).where(~mask, rvals)

if result._is_label_reference(name):
result[name] = key_col
Expand Down
6 changes: 3 additions & 3 deletions pandas/core/sparse/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
from pandas.util._decorators import Appender
from pandas.core.indexes.base import _index_shared_docs


_sparray_doc_kwargs = dict(klass='SparseArray')


Expand Down Expand Up @@ -259,6 +258,7 @@ def __array_wrap__(self, out_arr, context=None):
ufunc, args, domain = context
# to apply ufunc only to fill_value (to avoid recursive call)
args = [getattr(a, 'fill_value', a) for a in args]

with np.errstate(all='ignore'):
fill_value = ufunc(self.fill_value, *args[1:])
else:
Expand Down Expand Up @@ -292,9 +292,9 @@ def __setstate__(self, state):
self._fill_value = fill_value

def __len__(self):
try:
if hasattr(self, 'sp_index'):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This felt really icky to me to try and catch something... Feel free to push back on that change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How was this failing before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no clue! 😄 I couldn't figure out why the try / except was there in the first place tbh.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't essential to this PR. I just don't understand why this code was here in the first place.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meaning I can take it out to bring down the code changes

return self.sp_index.length
except:
else:
return 0

def __unicode__(self):
Expand Down
4 changes: 4 additions & 0 deletions pandas/core/sparse/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ def __init__(self, data=None, index=None, columns=None, default_kind=None,
if columns is None:
raise Exception("cannot pass a series w/o a name or columns")
data = {columns[0]: data}
elif isinstance(data, BlockManager):
fill_value_size = len(set(b.fill_value for b in data.blocks))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TomAugspurger @jreback

Ok I need feedback on this change. Basically what this does is: if every SparseSeries's fill_value is the same it sets the default_fill_value to that. This to me seems really intuitive, but I trust your judgement. It also makes testing a whole of a hell lot easier which is why I did it.

I would argue pretty heavily that as a user if I added sparse series that all had fill_value=0 then I would expect the sparse data frame to have default_fill_value = 0 as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes this is the only reason to have a standalone SparseDataFrame, meaning that they have the same default fill values. But its not worth the cost generally.

if default_fill_value is None and fill_value_size == 1:
default_fill_value = data.blocks[0].fill_value

if default_fill_value is None:
default_fill_value = np.nan
Expand Down
21 changes: 16 additions & 5 deletions pandas/core/sparse/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def values(self):

def __array__(self, result=None):
""" the array interface, return my values """
return self.block.values
return self.block.values.values
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will return the dense version of SparseSeries when calling np.array(sp)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need some more asv's for sparse I think


def get_values(self):
""" same as values """
Expand Down Expand Up @@ -271,6 +271,7 @@ def __array_wrap__(self, result, context=None):

See SparseArray.__array_wrap__ for detail.
"""

if isinstance(context, tuple) and len(context) == 3:
ufunc, args, domain = context
args = [getattr(a, 'fill_value', a) for a in args]
Expand All @@ -279,8 +280,18 @@ def __array_wrap__(self, result, context=None):
else:
fill_value = self.fill_value

# GH 14167
# Since we are returning a dense representation of
# SparseSeries sparse_index might not align when calling
# ufunc on the array. There doesn't seem to be a better way
# to do this unfortunately.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't intuitive to me but the array*_ functions defined in numpy don't work quite as expected in certain circumstances. np.abs(s) is the one that was throwing me for a loop.

s = SparseArray([1,-2,2,3], fill_value=-2)
np.abs(s)

What would happen to the dense version of the array would be that it would have 3 npoints when really it should only have 2. By zeroing out the sparse_index it fixes the problem, because it relies on the dense index instead going around the problem.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should simply operate on the sp_values, not the on the densified (or maybe its possible that some classes of ufuncs could operate on the dense, but not sure)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so the expected behavior is np.abs(SparseArray([1,-2,2,3])) == array([1,2,3]) ?

if len(result) != self.sp_index.npoints:
sparse_index = None
else:
sparse_index = self.sp_index

return self._constructor(result, index=self.index,
sparse_index=self.sp_index,
sparse_index=sparse_index,
fill_value=fill_value,
copy=False).__finalize__(self)

Expand Down Expand Up @@ -402,8 +413,8 @@ def abs(self):
-------
abs: type of caller
"""
return self._constructor(np.abs(self.values),
index=self.index).__finalize__(self)

return np.abs(self)

def get(self, label, default=None):
"""
Expand Down Expand Up @@ -544,7 +555,7 @@ def to_dense(self, sparse_only=False):
index = self.index.take(int_index.indices)
return Series(self.sp_values, index=index, name=self.name)
else:
return Series(self.values.to_dense(), index=self.index,
return Series(self.get_values(), index=self.index,
name=self.name)

@property
Expand Down
91 changes: 90 additions & 1 deletion pandas/tests/reshape/merge/test_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import numpy as np
import random
import re
import itertools

import pandas as pd
from pandas.compat import lrange, lzip
Expand All @@ -18,7 +19,10 @@
is_categorical_dtype,
is_object_dtype,
)
from pandas import DataFrame, Index, MultiIndex, Series, Categorical
from pandas import (
DataFrame, Index,
MultiIndex, Series, Categorical
)
import pandas.util.testing as tm
from pandas.api.types import CategoricalDtype as CDT

Expand Down Expand Up @@ -1802,3 +1806,88 @@ def test_merge_on_indexes(self, left_df, right_df, how, sort, expected):
how=how,
sort=sort)
tm.assert_frame_equal(result, expected)


class TestMergeSparseDataFrames(object):
@pytest.mark.parametrize('fill_value,how', itertools.product([0, 1,
None,
np.nan],
['left',
'right',
'outer',
'inner']))
def test_merge_two_sparse_frames(self, fill_value, how):
dense_evens = pd.DataFrame({'A': list(range(0, 200, 2)),
'B': np.random.randint(0, 100, size=100)})
dense_threes = pd.DataFrame({'A': list(range(0, 300, 3)),
'B': np.random.randint(0, 100, size=100)})

sparse_evens = dense_evens.to_sparse(fill_value=fill_value)
sparse_threes = dense_threes.to_sparse(fill_value=fill_value)

to_merge_sparse = [sparse_evens, sparse_threes]

to_merge_dense = [dense_evens, dense_threes]

for _ in range(2):
sparse_merge = to_merge_sparse[0].merge(to_merge_sparse[1],
how=how, on='A')

dense_merge = to_merge_dense[0].merge(to_merge_dense[1],
how=how, on='A')

# If you merge two dense frames together it tends to default to
# float64 not the original dtype
dense_merge['B_x'] = dense_merge['B_x'].astype(dense_evens.A.dtype,
errors='ignore')
dense_merge['B_y'] = dense_merge['B_y'].astype(dense_evens.A.dtype,
errors='ignore')

if fill_value is None or fill_value is np.nan:
assert sparse_merge.default_fill_value is np.nan
else:
tm.assert_almost_equal(sparse_merge.default_fill_value,
fill_value)

exp = dense_merge.to_sparse(fill_value=fill_value)
tm.assert_sp_frame_equal(sparse_merge, exp,
exact_indices=False,
check_dtype=False)

to_merge_sparse = to_merge_sparse[::-1]
to_merge_dense = to_merge_dense[::-1]

@pytest.mark.parametrize('fill_value,how', itertools.product([0, 1,
None,
np.nan],
['left',
'right',
'outer',
'inner']))
def test_merge_dense_sparse_frames(self, fill_value, how):
fill_value = np.nan

dense_evens = pd.DataFrame({'A': list(range(0, 200, 2)),
'B': np.random.randint(0, 100, size=100)})

dense_threes = pd.DataFrame({'A': list(range(0, 300, 3)),
'B': np.random.randint(0, 100, size=100)})

sparse_evens = dense_evens.to_sparse(fill_value=fill_value)

to_merge = [sparse_evens, dense_threes]
to_merge_dense = [dense_evens, dense_threes]

for _ in range(2):
merged = to_merge[0].merge(to_merge[1], how=how, on='A')

dense_merge = to_merge_dense[0].merge(to_merge_dense[1],
how=how, on='A')

for column in dense_merge.columns:
dense_col = merged[column].to_dense()
tm.assert_series_equal(dense_col, dense_merge[column],
check_dtype=False)

to_merge = to_merge[::-1]
to_merge_dense = to_merge_dense[::-1]
21 changes: 14 additions & 7 deletions pandas/tests/sparse/frame/test_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,27 +222,34 @@ class Unknown:
'"Unknown" for data argument'):
SparseDataFrame(Unknown())

def test_constructor_preserve_attr(self):
# Cannot use None as a fill_value cause it will overwrite as zeros
@pytest.mark.parametrize('fill_value', [0, 1, np.nan])
def test_constructor_preserve_attr(self, fill_value):
# GH 13866
arr = pd.SparseArray([1, 0, 3, 0], dtype=np.int64, fill_value=0)
arr = pd.SparseArray([1, 0, 3, 0], dtype=np.int64,
fill_value=fill_value)

assert arr.dtype == np.int64
assert arr.fill_value == 0
tm.assert_almost_equal(arr.fill_value, fill_value)

df = pd.SparseDataFrame({'x': arr})
assert df['x'].dtype == np.int64
assert df['x'].fill_value == 0

tm.assert_almost_equal(df['x'].fill_value, fill_value)

s = pd.SparseSeries(arr, name='x')
assert s.dtype == np.int64
assert s.fill_value == 0
tm.assert_almost_equal(s.fill_value, fill_value)

df = pd.SparseDataFrame(s)
assert df['x'].dtype == np.int64
assert df['x'].fill_value == 0

tm.assert_almost_equal(df['x'].fill_value, fill_value)

df = pd.SparseDataFrame({'x': s})
assert df['x'].dtype == np.int64
assert df['x'].fill_value == 0

tm.assert_almost_equal(df['x'].fill_value, fill_value)

def test_constructor_nan_dataframe(self):
# GH 10079
Expand Down
10 changes: 6 additions & 4 deletions pandas/tests/sparse/series/test_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,10 +626,12 @@ def _check_inplace_op(iop, op):
getattr(operator, op))

def test_abs(self):
s = SparseSeries([1, 2, -3], name='x')
expected = SparseSeries([1, 2, 3], name='x')
s = SparseSeries([-1, -2, -3, None, np.nan], name='x')
expected = SparseSeries([1, 2, 3, None, np.nan], name='x')
result = s.abs()
tm.assert_sp_series_equal(result, expected)
assert result.npoints == expected.npoints
assert result.npoints == len(result.sp_values)
assert result.name == 'x'

result = abs(s)
Expand All @@ -641,9 +643,9 @@ def test_abs(self):
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')
expected = SparseSeries([1, 2, 2, 3], fill_value=2, name='x')
result = s.abs()

tm.assert_sp_series_equal(result, expected)
assert result.name == 'x'

Expand Down
Loading