Skip to content

Commit 6cb6858

Browse files
BUG: Changed .at to not set values that do not exist yet in a DataFrame pandas-dev#48323
1 parent 4e72340 commit 6cb6858

File tree

6 files changed

+59
-27
lines changed

6 files changed

+59
-27
lines changed

doc/source/user_guide/indexing.rst

+3-7
Original file line numberDiff line numberDiff line change
@@ -851,16 +851,12 @@ You can also set using these same indexers.
851851

852852
.. ipython:: python
853853
854-
df.at[dates[5], 'E'] = 7
854+
df.at[dates[5], 'D'] = 7
855855
df.iat[3, 0] = 7
856-
857-
``at`` may enlarge the object in-place as above if the indexer is missing.
858-
859-
.. ipython:: python
860-
861-
df.at[dates[-1] + pd.Timedelta('1 day'), 0] = 7
862856
df
863857
858+
``at`` will not enlarge the object in-place if the indexer is missing.
859+
864860
Boolean indexing
865861
----------------
866862

doc/source/whatsnew/v1.6.0.rst

+34-3
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,41 @@ Notable bug fixes
3939

4040
These are bug fixes that might have notable behavior changes.
4141

42-
.. _whatsnew_160.notable_bug_fixes.notable_bug_fix1:
42+
.. _whatsnew_160.notable_bug_fixes.at_DataFrame_expand:
4343

44-
notable_bug_fix1
45-
^^^^^^^^^^^^^^^^
44+
Using DataFrame.at to expand DataFrame
45+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
46+
47+
:meth:`DataFrame.at` would allow the addition of columns and rows to a DataFrame. (:issue:`48323`)
48+
49+
.. code-block:: ipython
50+
51+
In [3]: frame = pd.DataFrame({"a": [1, 2]})
52+
In [4]: frame
53+
Out[4]:
54+
a
55+
0 1
56+
1 2
57+
58+
*Old Behavior*
59+
60+
.. code-block:: ipython
61+
62+
In [5]: frame.at[2, "a"] = 7
63+
In [6]: frame
64+
Out[6]:
65+
a
66+
0 1
67+
1 2
68+
2 7
69+
70+
*New Behavior*
71+
72+
.. code-block:: ipython
73+
74+
In [5]: frame.at[2, "a"] = 7
75+
Out[5]:
76+
KeyError: 2
4677
4778
.. _whatsnew_160.notable_bug_fixes.notable_bug_fix2:
4879

pandas/core/indexing.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -551,16 +551,15 @@ def at(self) -> _AtIndexer:
551551
Raises
552552
------
553553
KeyError
554-
* If getting a value and 'label' does not exist in a DataFrame or
555-
Series.
554+
* If getting or setting a value and 'label' does not exist in a
555+
DataFrame or Series.
556556
ValueError
557557
* If row/column label pair is not a tuple or if any label from
558558
the pair is not a scalar for DataFrame.
559559
* If label is list-like (*excluding* NamedTuple) for Series.
560560
561561
See Also
562562
--------
563-
DataFrame.at : Access a single value for a row/column pair by label.
564563
DataFrame.iat : Access a single value for a row/column pair by integer
565564
position.
566565
DataFrame.loc : Access a group of rows and columns by label(s).
@@ -2429,6 +2428,9 @@ def __getitem__(self, key):
24292428
return super().__getitem__(key)
24302429

24312430
def __setitem__(self, key, value):
2431+
# raises exception if key does not exist
2432+
self.__getitem__(key)
2433+
24322434
if self.ndim == 2 and not self._axes_are_unique:
24332435
# GH#33041 fall back to .loc
24342436
if not isinstance(key, tuple) or not all(is_scalar(x) for x in key):

pandas/tests/indexes/multi/test_get_set.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ def test_set_value_keeps_names():
415415
df = df.sort_index()
416416
assert df._is_copy is None
417417
assert df.index.names == ("Name", "Number")
418-
df.at[("grethe", "4"), "one"] = 99.34
418+
df.loc[("grethe", "4"), "one"] = 99.34
419419
assert df._is_copy is None
420420
assert df.index.names == ("Name", "Number")
421421

pandas/tests/indexing/test_at.py

+16-7
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
import numpy as np
77
import pytest
88

9-
from pandas.errors import InvalidIndexError
10-
119
from pandas import (
1210
CategoricalDtype,
1311
CategoricalIndex,
@@ -105,12 +103,15 @@ def test_at_setitem_multiindex(self):
105103
np.zeros((3, 2), dtype="int64"),
106104
columns=MultiIndex.from_tuples([("a", 0), ("a", 1)]),
107105
)
108-
df.at[0, "a"] = 10
106+
df.at[0, ("a", 0)] = 10
107+
df.at[0, ("a", 1)] = 10
109108
expected = DataFrame(
110109
[[10, 10], [0, 0], [0, 0]],
111110
columns=MultiIndex.from_tuples([("a", 0), ("a", 1)]),
112111
)
113112
tm.assert_frame_equal(df, expected)
113+
with pytest.raises(TypeError, match=""):
114+
df.at[0, "a"] = 11
114115

115116
@pytest.mark.parametrize("row", (Timestamp("2019-01-01"), "2019-01-01"))
116117
def test_at_datetime_index(self, row):
@@ -126,11 +127,13 @@ def test_at_datetime_index(self, row):
126127
tm.assert_frame_equal(df, expected)
127128

128129

129-
class TestAtSetItemWithExpansion:
130-
def test_at_setitem_expansion_series_dt64tz_value(self, tz_naive_fixture):
130+
class TestAtSetTzItem:
131+
def test_at_setitem_series_dt64tz_value(self, tz_naive_fixture):
131132
# GH#25506
133+
# Modified in GH#48323 due to .at change
132134
ts = Timestamp("2017-08-05 00:00:00+0100", tz=tz_naive_fixture)
133-
result = Series(ts)
135+
ts2 = Timestamp("2017-09-05 00:00:00+0100", tz=tz_naive_fixture)
136+
result = Series([ts, ts2])
134137
result.at[1] = ts
135138
expected = Series([ts, ts])
136139
tm.assert_series_equal(result, expected)
@@ -211,7 +214,7 @@ def test_at_frame_raises_key_error2(self, indexer_al):
211214
def test_at_frame_multiple_columns(self):
212215
# GH#48296 - at shouldn't modify multiple columns
213216
df = DataFrame({"a": [1, 2], "b": [3, 4]})
214-
with pytest.raises(InvalidIndexError, match=r"slice\(None, None, None\)"):
217+
with pytest.raises(TypeError, match="col"):
215218
df.at[5] = [6, 7]
216219

217220
def test_at_getitem_mixed_index_no_fallback(self):
@@ -234,3 +237,9 @@ def test_at_categorical_integers(self):
234237
for key in [0, 1]:
235238
with pytest.raises(KeyError, match=str(key)):
236239
df.at[key, key]
240+
241+
def test_at_does_not_expand(self):
242+
# GH#48323
243+
frame = DataFrame({"a": [1, 2]})
244+
with pytest.raises(KeyError, match="b"):
245+
frame.at[2, "b"] = 9

pandas/tests/indexing/test_partial.py

-6
Original file line numberDiff line numberDiff line change
@@ -343,19 +343,13 @@ def test_partial_setting2(self):
343343
df = df_orig.copy()
344344
df.loc[dates[-1] + dates.freq, "A"] = 7
345345
tm.assert_frame_equal(df, expected)
346-
df = df_orig.copy()
347-
df.at[dates[-1] + dates.freq, "A"] = 7
348-
tm.assert_frame_equal(df, expected)
349346

350347
exp_other = DataFrame({0: 7}, index=dates[-1:] + dates.freq)
351348
expected = pd.concat([df_orig, exp_other], axis=1)
352349

353350
df = df_orig.copy()
354351
df.loc[dates[-1] + dates.freq, 0] = 7
355352
tm.assert_frame_equal(df, expected)
356-
df = df_orig.copy()
357-
df.at[dates[-1] + dates.freq, 0] = 7
358-
tm.assert_frame_equal(df, expected)
359353

360354
def test_partial_setting_mixed_dtype(self):
361355

0 commit comments

Comments
 (0)