Skip to content

Commit 17212ac

Browse files
committed
fixup! ENH: Allow SparseDataFrame/SparseSeries values assignment
1 parent 13a033e commit 17212ac

File tree

6 files changed

+27
-40
lines changed

6 files changed

+27
-40
lines changed

pandas/core/sparse/array.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -382,26 +382,25 @@ def set_values(self, indexer, value):
382382
# If indexer is not a single int position, easiest to handle via dense
383383
if not is_scalar(indexer):
384384
warnings.warn(
385-
'Setting SparseSeries/Array values is particularly '
386-
'inefficient when indexing with multiple keys because the '
387-
'whole series is made dense interim.',
385+
'Setting SparseSeries/Array values is inefficient when '
386+
'indexing with multiple keys because the whole series '
387+
'is made dense interim.',
388388
PerformanceWarning, stacklevel=2)
389389

390390
values = self.to_dense()
391391
values[indexer] = value
392392
return SparseArray(values, kind=self.kind,
393393
fill_value=self.fill_value)
394394

395-
warnings.warn(
396-
'Setting SparseSeries/Array values is inefficient '
397-
'(a copy of data is made).', PerformanceWarning, stacklevel=2)
398-
399395
# If label already in sparse index, just switch the value on a copy
400396
idx = self.sp_index.lookup(indexer)
401397
if idx != -1:
402-
obj = self.copy()
403-
obj.sp_values[idx] = value
404-
return obj
398+
self.sp_values[idx] = value
399+
return self
400+
401+
warnings.warn(
402+
'Setting new SparseSeries values is inefficient '
403+
'(a copy of data is made).', PerformanceWarning, stacklevel=2)
405404

406405
# Otherwise, construct a new array, and insert the new value in the
407406
# correct position
@@ -410,6 +409,7 @@ def set_values(self, indexer, value):
410409

411410
indices = np.insert(indices, pos, indexer)
412411
sp_values = np.insert(self.sp_values, pos, value)
412+
413413
# Length can be increased when adding a new value into index
414414
length = max(self.sp_index.length, indexer + 1)
415415
sp_index = _make_index(length, indices, self.kind)

pandas/core/sparse/series.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -276,11 +276,13 @@ def __array_wrap__(self, result, context=None):
276276
else:
277277
fill_value = self.fill_value
278278

279-
# Assume: If result size matches, old sparse index is valid (ok???)
279+
# Only reuse old sparse index if result size matches
280+
# (fails e.g. for ~sparseseries)
280281
if np.size(result) == self.sp_index.npoints:
281282
sp_index = self.sp_index
282283
else:
283284
sp_index = None
285+
284286
return self._constructor(result, index=self.index,
285287
sparse_index=sp_index,
286288
fill_value=fill_value,
@@ -489,10 +491,10 @@ def set_value(self, label, value, takeable=False):
489491
"in a future release. Please use "
490492
".at[] or .iat[] accessors instead", FutureWarning,
491493
stacklevel=2)
494+
self._data = self._data.copy()
492495
return self._set_value(label, value, takeable=takeable)
493496

494497
def _set_value(self, label, value, takeable=False):
495-
self._data = self._data.copy()
496498
try:
497499
idx = self.index.get_loc(label)
498500
except KeyError:

pandas/tests/sparse/frame/test_frame.py

+7-14
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
from pandas import Series, DataFrame, bdate_range, Panel
1212
from pandas.core.indexes.datetimes import DatetimeIndex
13-
from pandas.errors import PerformanceWarning
1413
from pandas.tseries.offsets import BDay
1514
from pandas.util import testing as tm
1615
from pandas.compat import lrange
@@ -461,33 +460,28 @@ def test_set_value(self):
461460
# ok, as the index gets converted to object
462461
frame = self.frame.copy()
463462
with tm.assert_produces_warning(FutureWarning,
464-
check_stacklevel=False,
465-
ignore_extra=True):
463+
check_stacklevel=False):
466464
res = frame.set_value('foobar', 'B', 1.5)
467465
assert res.index.dtype == 'object'
468466

469467
res = self.frame
470468
res.index = res.index.astype(object)
471469

472470
with tm.assert_produces_warning(FutureWarning,
473-
check_stacklevel=False,
474-
ignore_extra=True):
471+
check_stacklevel=False):
475472
res = self.frame.set_value('foobar', 'B', 1.5)
476473
assert res.index[-1] == 'foobar'
477474
with tm.assert_produces_warning(FutureWarning,
478-
check_stacklevel=False,
479-
ignore_extra=True):
475+
check_stacklevel=False):
480476
assert res.get_value('foobar', 'B') == 1.5
481477

482478
with tm.assert_produces_warning(FutureWarning,
483-
check_stacklevel=False,
484-
ignore_extra=True):
479+
check_stacklevel=False):
485480
res2 = res.set_value('foobar', 'qux', 1.5)
486481
tm.assert_index_equal(res2.columns,
487482
pd.Index(list(self.frame.columns)))
488483
with tm.assert_produces_warning(FutureWarning,
489-
check_stacklevel=False,
490-
ignore_extra=True):
484+
check_stacklevel=False):
491485
assert res2.get_value('foobar', 'qux') == 1.5
492486

493487
def test_fancy_index_misc(self):
@@ -594,9 +588,8 @@ def test_setitem_chained_no_consolidate(self):
594588
# issuecomment-361696418
595589
# chained setitem used to cause consolidation
596590
sdf = pd.SparseDataFrame([[np.nan, 1], [2, np.nan]])
597-
with tm.assert_produces_warning(PerformanceWarning):
598-
with pd.option_context('mode.chained_assignment', None):
599-
sdf[0][1] = 2
591+
with pd.option_context('mode.chained_assignment', None):
592+
sdf[0][1] = 2
600593
assert len(sdf._data.blocks) == 2
601594

602595
def test_delitem(self):

pandas/tests/sparse/series/test_series.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -486,14 +486,12 @@ def test_get_get_value(self):
486486
def test_set_value(self):
487487
idx = self.btseries.index[7]
488488
with tm.assert_produces_warning(FutureWarning,
489-
check_stacklevel=False,
490-
ignore_extra=True):
489+
check_stacklevel=False):
491490
self.btseries.set_value(idx, 0)
492491
assert self.btseries[idx] == 0
493492

494493
with tm.assert_produces_warning(FutureWarning,
495-
check_stacklevel=False,
496-
ignore_extra=True):
494+
check_stacklevel=False):
497495
self.iseries.set_value('foobar', 0)
498496
assert self.iseries.index[-1] == 'foobar'
499497
assert self.iseries['foobar'] == 0

pandas/tests/sparse/test_format.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from pandas.compat import (is_platform_windows,
99
is_platform_32bit)
1010
from pandas.core.config import option_context
11-
from pandas.errors import PerformanceWarning
1211

1312
use_32bit_repr = is_platform_windows() or is_platform_32bit()
1413

@@ -124,10 +123,9 @@ def test_sparse_repr_after_set(self):
124123
sdf = pd.SparseDataFrame([[np.nan, 1], [2, np.nan]])
125124
res = sdf.copy()
126125

127-
with tm.assert_produces_warning(PerformanceWarning):
128-
# Ignore the warning
129-
with pd.option_context('mode.chained_assignment', None):
130-
sdf[0][1] = 2 # This line triggers the bug
126+
# Ignore the warning
127+
with pd.option_context('mode.chained_assignment', None):
128+
sdf[0][1] = 2 # This line triggers the bug
131129

132130
repr(sdf)
133131
tm.assert_sp_frame_equal(sdf, res)

pandas/util/testing.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -2428,8 +2428,7 @@ def exception_matches(self, exc_type, exc_value, trace_back):
24282428

24292429
@contextmanager
24302430
def assert_produces_warning(expected_warning=Warning, filter_level="always",
2431-
clear=None, check_stacklevel=True,
2432-
ignore_extra=False):
2431+
clear=None, check_stacklevel=True):
24332432
"""
24342433
Context manager for running code expected to either raise a specific
24352434
warning, or not raise any warnings. Verifies that the code raises the
@@ -2466,8 +2465,6 @@ class for all warnings. To check that no warning is returned,
24662465
If True, displays the line that called the function containing
24672466
the warning to show were the function is called. Otherwise, the
24682467
line that implements the function is displayed.
2469-
ignore_extra : bool, default False
2470-
If False, any extra, unexpected warnings are raised as errors.
24712468
24722469
Examples
24732470
--------
@@ -2534,7 +2531,6 @@ class for all warnings. To check that no warning is returned,
25342531
msg = "Did not see expected warning of class {name!r}.".format(
25352532
name=expected_warning.__name__)
25362533
assert saw_warning, msg
2537-
if not ignore_extra:
25382534
assert not extra_warnings, ("Caused unexpected warning(s): {extra!r}."
25392535
).format(extra=extra_warnings)
25402536

0 commit comments

Comments
 (0)