From 482a87ffa1278e42400a35b864816d220d5ddff6 Mon Sep 17 00:00:00 2001 From: Marc Garcia Date: Wed, 21 Dec 2022 13:43:18 +0700 Subject: [PATCH 01/10] Rename duplicate column names in read_json(orient='split') --- doc/source/whatsnew/v2.0.0.rst | 2 +- pandas/io/common.py | 70 +++++++++++++++++++++++++++ pandas/io/json/_json.py | 4 ++ pandas/io/parsers/base_parser.py | 59 ++-------------------- pandas/io/parsers/c_parser_wrapper.py | 17 +++++-- pandas/io/parsers/python_parser.py | 14 +++++- pandas/tests/io/json/test_pandas.py | 1 + 7 files changed, 105 insertions(+), 62 deletions(-) diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index 208bbfa10b9b2..da563b04ada48 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -489,7 +489,7 @@ Other API changes new DataFrame (shallow copy) instead of the original DataFrame, consistent with other methods to get a full slice (for example ``df.loc[:]`` or ``df[:]``) (:issue:`49469`) - Disallow computing ``cumprod`` for :class:`Timedelta` object; previously this returned incorrect values (:issue:`50246`) -- +- Loading a JSON file with duplicate columns using ``read_json(orient='split')`` renames columns to avoid duplicates, as :func:`read_csv` and the other readers do (:issue:`XXX`) .. --------------------------------------------------------------------------- .. _whatsnew_200.deprecations: diff --git a/pandas/io/common.py b/pandas/io/common.py index 4dae46c8f39f6..4e42f59e42f17 100644 --- a/pandas/io/common.py +++ b/pandas/io/common.py @@ -6,6 +6,7 @@ abstractmethod, ) import codecs +from collections import defaultdict import dataclasses import functools import gzip @@ -26,7 +27,9 @@ IO, Any, AnyStr, + DefaultDict, Generic, + Hashable, Literal, Mapping, Sequence, @@ -67,6 +70,7 @@ is_list_like, ) +from pandas.core.indexes.api import MultiIndex from pandas.core.shared_docs import _shared_docs _VALID_URLS = set(uses_relative + uses_netloc + uses_params) @@ -1181,3 +1185,69 @@ def _get_binary_io_classes() -> tuple[type, ...]: binary_classes += (type(reader),) return binary_classes + + +def is_potential_multi_index( + columns: Sequence[Hashable] | MultiIndex, + index_col: bool | Sequence[int] | None = None, +) -> bool: + """ + Check whether or not the `columns` parameter + could be converted into a MultiIndex. + + Parameters + ---------- + columns : array-like + Object which may or may not be convertible into a MultiIndex + index_col : None, bool or list, optional + Column or columns to use as the (possibly hierarchical) index + + Returns + ------- + bool : Whether or not columns could become a MultiIndex + """ + if index_col is None or isinstance(index_col, bool): + index_col = [] + + return bool( + len(columns) + and not isinstance(columns, MultiIndex) + and all(isinstance(c, tuple) for c in columns if c not in list(index_col)) + ) + + +def dedup_names( + names: Sequence[Hashable], is_potential_multi_index: bool +) -> Sequence[Hashable]: + """ + Rename column names if duplicates exist. + + Currently the renaming is done by appending a period and an autonumeric, + but a custom pattern may be supported in the future. + + Examples + -------- + >>> dedup_names(["x", "y", "x", "x"]) + ['x', 'y', 'x.1', 'x.2'] + """ + names = list(names) # so we can index + counts: DefaultDict[Hashable, int] = defaultdict(int) + + for i, col in enumerate(names): + cur_count = counts[col] + + while cur_count > 0: + counts[col] = cur_count + 1 + + if is_potential_multi_index: + # for mypy + assert isinstance(col, tuple) + col = col[:-1] + (f"{col[-1]}.{cur_count}",) + else: + col = f"{col}.{cur_count}" + cur_count = counts[col] + + names[i] = col + counts[col] = cur_count + 1 + + return names diff --git a/pandas/io/json/_json.py b/pandas/io/json/_json.py index f2780d5fa6832..f644ec8048b63 100644 --- a/pandas/io/json/_json.py +++ b/pandas/io/json/_json.py @@ -58,6 +58,7 @@ from pandas.io.common import ( IOHandles, _extension_to_compression, + dedup_names, file_exists, get_handle, is_fsspec_url, @@ -1245,6 +1246,9 @@ def _parse(self) -> None: str(k): v for k, v in loads(json, precise_float=self.precise_float).items() } + decoded["columns"] = dedup_names( + names=decoded["columns"], is_potential_multi_index=False + ) self.check_keys_split(decoded) self.obj = DataFrame(dtype=None, **decoded) elif orient == "index": diff --git a/pandas/io/parsers/base_parser.py b/pandas/io/parsers/base_parser.py index 2f6ee34092541..8b11841bdda8a 100644 --- a/pandas/io/parsers/base_parser.py +++ b/pandas/io/parsers/base_parser.py @@ -10,7 +10,6 @@ TYPE_CHECKING, Any, Callable, - DefaultDict, Hashable, Iterable, List, @@ -89,6 +88,8 @@ from pandas.core.series import Series from pandas.core.tools import datetimes as tools +from pandas.io.common import is_potential_multi_index + if TYPE_CHECKING: from pandas import DataFrame @@ -333,31 +334,6 @@ def extract(r): return names, index_names, col_names, passed_names - @final - def _dedup_names(self, names: Sequence[Hashable]) -> Sequence[Hashable]: - names = list(names) # so we can index - counts: DefaultDict[Hashable, int] = defaultdict(int) - is_potential_mi = _is_potential_multi_index(names, self.index_col) - - for i, col in enumerate(names): - cur_count = counts[col] - - while cur_count > 0: - counts[col] = cur_count + 1 - - if is_potential_mi: - # for mypy - assert isinstance(col, tuple) - col = col[:-1] + (f"{col[-1]}.{cur_count}",) - else: - col = f"{col}.{cur_count}" - cur_count = counts[col] - - names[i] = col - counts[col] = cur_count + 1 - - return names - @final def _maybe_make_multi_index_columns( self, @@ -365,7 +341,7 @@ def _maybe_make_multi_index_columns( col_names: Sequence[Hashable] | None = None, ) -> Sequence[Hashable] | MultiIndex: # possibly create a column mi here - if _is_potential_multi_index(columns): + if is_potential_multi_index(columns): list_columns = cast(List[Tuple], columns) return MultiIndex.from_tuples(list_columns, names=col_names) return columns @@ -1332,35 +1308,6 @@ def _get_na_values(col, na_values, na_fvalues, keep_default_na): return na_values, na_fvalues -def _is_potential_multi_index( - columns: Sequence[Hashable] | MultiIndex, - index_col: bool | Sequence[int] | None = None, -) -> bool: - """ - Check whether or not the `columns` parameter - could be converted into a MultiIndex. - - Parameters - ---------- - columns : array-like - Object which may or may not be convertible into a MultiIndex - index_col : None, bool or list, optional - Column or columns to use as the (possibly hierarchical) index - - Returns - ------- - bool : Whether or not columns could become a MultiIndex - """ - if index_col is None or isinstance(index_col, bool): - index_col = [] - - return bool( - len(columns) - and not isinstance(columns, MultiIndex) - and all(isinstance(c, tuple) for c in columns if c not in list(index_col)) - ) - - def _validate_parse_dates_arg(parse_dates): """ Check whether or not the 'parse_dates' parameter diff --git a/pandas/io/parsers/c_parser_wrapper.py b/pandas/io/parsers/c_parser_wrapper.py index e0daf157d3d3a..12f35c4815582 100644 --- a/pandas/io/parsers/c_parser_wrapper.py +++ b/pandas/io/parsers/c_parser_wrapper.py @@ -30,6 +30,10 @@ from pandas.core.indexes.api import ensure_index_from_sequences +from pandas.io.common import ( + dedup_names, + is_potential_multi_index, +) from pandas.io.parsers.base_parser import ( ParserBase, ParserError, @@ -227,7 +231,10 @@ def read( except StopIteration: if self._first_chunk: self._first_chunk = False - names = self._dedup_names(self.orig_names) + names = dedup_names( + self.orig_names, + is_potential_multi_index(self.orig_names, self.index_col), + ) index, columns, col_dict = self._get_empty_meta( names, self.index_col, @@ -281,7 +288,9 @@ def read( if self.usecols is not None: names = self._filter_usecols(names) - names = self._dedup_names(names) + names = self._dedup_names( + names, is_potential_multi_index(names, self.index_col) + ) # rename dict keys data_tups = sorted(data.items()) @@ -303,7 +312,9 @@ def read( # assert for mypy, orig_names is List or None, None would error in list(...) assert self.orig_names is not None names = list(self.orig_names) - names = self._dedup_names(names) + names = self._dedup_names( + names, is_potential_multi_index(names, self.index_col) + ) if self.usecols is not None: names = self._filter_usecols(names) diff --git a/pandas/io/parsers/python_parser.py b/pandas/io/parsers/python_parser.py index aebf285e669bb..96143fad1b521 100644 --- a/pandas/io/parsers/python_parser.py +++ b/pandas/io/parsers/python_parser.py @@ -37,6 +37,10 @@ from pandas.core.dtypes.common import is_integer from pandas.core.dtypes.inference import is_dict_like +from pandas.io.common import ( + dedup_names, + is_potential_multi_index, +) from pandas.io.parsers.base_parser import ( ParserBase, parser_defaults, @@ -259,7 +263,10 @@ def read( columns: Sequence[Hashable] = list(self.orig_names) if not len(content): # pragma: no cover # DataFrame with the right metadata, even though it's length 0 - names = self._dedup_names(self.orig_names) + names = dedup_names( + self.orig_names, + is_potential_multi_index(self.orig_names, self.index_col), + ) # error: Cannot determine type of 'index_col' index, columns, col_dict = self._get_empty_meta( names, @@ -293,7 +300,9 @@ def _exclude_implicit_index( self, alldata: list[np.ndarray], ) -> tuple[Mapping[Hashable, np.ndarray], Sequence[Hashable]]: - names = self._dedup_names(self.orig_names) + names = self._dedup_names( + self.orig_names, is_potential_multi_index(self.orig_names, self.index_col) + ) offset = 0 if self._implicit_index: @@ -434,6 +443,7 @@ def _infer_columns( if i not in this_unnamed_cols ] + this_unnamed_cols + # TODO: Use pandas.io.common.dedup_names instead for i in col_loop_order: col = this_columns[i] old_col = col diff --git a/pandas/tests/io/json/test_pandas.py b/pandas/tests/io/json/test_pandas.py index 4edd08014050e..6ee398811ecb1 100644 --- a/pandas/tests/io/json/test_pandas.py +++ b/pandas/tests/io/json/test_pandas.py @@ -117,6 +117,7 @@ def test_frame_non_unique_columns(self, orient, data): expected.iloc[:, 0] = expected.iloc[:, 0].view(np.int64) // 1000000 elif orient == "split": expected = df + expected.columns = ["x", "x.1"] tm.assert_frame_equal(result, expected) From c89cba5edf4cd3481acf87b2d35be4a3e0816df1 Mon Sep 17 00:00:00 2001 From: Marc Garcia Date: Wed, 21 Dec 2022 14:00:09 +0700 Subject: [PATCH 02/10] Add issue number to TODO --- pandas/io/parsers/python_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/parsers/python_parser.py b/pandas/io/parsers/python_parser.py index 96143fad1b521..838a544430069 100644 --- a/pandas/io/parsers/python_parser.py +++ b/pandas/io/parsers/python_parser.py @@ -443,7 +443,7 @@ def _infer_columns( if i not in this_unnamed_cols ] + this_unnamed_cols - # TODO: Use pandas.io.common.dedup_names instead + # TODO: Use pandas.io.common.dedup_names instead (see #50371) for i in col_loop_order: col = this_columns[i] old_col = col From fbc093dcf231f120af4d8a53dbc7760679250b51 Mon Sep 17 00:00:00 2001 From: Marc Garcia Date: Mon, 26 Dec 2022 14:41:54 +0700 Subject: [PATCH 03/10] Finish renaming of _dedup_names and check keys before column renaming --- pandas/io/json/_json.py | 2 +- pandas/io/parsers/c_parser_wrapper.py | 4 ++-- pandas/io/parsers/python_parser.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/io/json/_json.py b/pandas/io/json/_json.py index f644ec8048b63..2f6f4ac1c3686 100644 --- a/pandas/io/json/_json.py +++ b/pandas/io/json/_json.py @@ -1246,10 +1246,10 @@ def _parse(self) -> None: str(k): v for k, v in loads(json, precise_float=self.precise_float).items() } + self.check_keys_split(decoded) decoded["columns"] = dedup_names( names=decoded["columns"], is_potential_multi_index=False ) - self.check_keys_split(decoded) self.obj = DataFrame(dtype=None, **decoded) elif orient == "index": self.obj = DataFrame.from_dict( diff --git a/pandas/io/parsers/c_parser_wrapper.py b/pandas/io/parsers/c_parser_wrapper.py index 12f35c4815582..8ef0e5f82ab41 100644 --- a/pandas/io/parsers/c_parser_wrapper.py +++ b/pandas/io/parsers/c_parser_wrapper.py @@ -288,7 +288,7 @@ def read( if self.usecols is not None: names = self._filter_usecols(names) - names = self._dedup_names( + names = dedup_names( names, is_potential_multi_index(names, self.index_col) ) @@ -312,7 +312,7 @@ def read( # assert for mypy, orig_names is List or None, None would error in list(...) assert self.orig_names is not None names = list(self.orig_names) - names = self._dedup_names( + names = dedup_names( names, is_potential_multi_index(names, self.index_col) ) diff --git a/pandas/io/parsers/python_parser.py b/pandas/io/parsers/python_parser.py index 838a544430069..59ea8e86f29dd 100644 --- a/pandas/io/parsers/python_parser.py +++ b/pandas/io/parsers/python_parser.py @@ -300,7 +300,7 @@ def _exclude_implicit_index( self, alldata: list[np.ndarray], ) -> tuple[Mapping[Hashable, np.ndarray], Sequence[Hashable]]: - names = self._dedup_names( + names = dedup_names( self.orig_names, is_potential_multi_index(self.orig_names, self.index_col) ) From 6d30477ed2957537201b6ead3cea473371402243 Mon Sep 17 00:00:00 2001 From: Marc Garcia Date: Mon, 26 Dec 2022 14:50:57 +0700 Subject: [PATCH 04/10] Black --- pandas/io/parsers/c_parser_wrapper.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pandas/io/parsers/c_parser_wrapper.py b/pandas/io/parsers/c_parser_wrapper.py index 8ef0e5f82ab41..551518b623836 100644 --- a/pandas/io/parsers/c_parser_wrapper.py +++ b/pandas/io/parsers/c_parser_wrapper.py @@ -288,9 +288,7 @@ def read( if self.usecols is not None: names = self._filter_usecols(names) - names = dedup_names( - names, is_potential_multi_index(names, self.index_col) - ) + names = dedup_names(names, is_potential_multi_index(names, self.index_col)) # rename dict keys data_tups = sorted(data.items()) @@ -312,9 +310,7 @@ def read( # assert for mypy, orig_names is List or None, None would error in list(...) assert self.orig_names is not None names = list(self.orig_names) - names = dedup_names( - names, is_potential_multi_index(names, self.index_col) - ) + names = dedup_names(names, is_potential_multi_index(names, self.index_col)) if self.usecols is not None: names = self._filter_usecols(names) From 4252e2f740faf5be95e5825b86f23d9d85034c17 Mon Sep 17 00:00:00 2001 From: Marc Garcia Date: Wed, 28 Dec 2022 12:37:51 +0700 Subject: [PATCH 05/10] Support multiindex and fix linters --- pandas/io/common.py | 4 ++-- pandas/io/json/_json.py | 8 +++++++- pandas/io/parsers/python_parser.py | 13 +++++++++++-- pandas/tests/io/json/test_pandas.py | 21 +++++++++++++++++++++ 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/pandas/io/common.py b/pandas/io/common.py index 4e42f59e42f17..eabae36e5a907 100644 --- a/pandas/io/common.py +++ b/pandas/io/common.py @@ -1217,7 +1217,7 @@ def is_potential_multi_index( def dedup_names( - names: Sequence[Hashable], is_potential_multi_index: bool + names: Sequence[Hashable], is_potential_multiindex: bool ) -> Sequence[Hashable]: """ Rename column names if duplicates exist. @@ -1239,7 +1239,7 @@ def dedup_names( while cur_count > 0: counts[col] = cur_count + 1 - if is_potential_multi_index: + if is_potential_multiindex: # for mypy assert isinstance(col, tuple) col = col[:-1] + (f"{col[-1]}.{cur_count}",) diff --git a/pandas/io/json/_json.py b/pandas/io/json/_json.py index 2f6f4ac1c3686..c9a1e537b8f48 100644 --- a/pandas/io/json/_json.py +++ b/pandas/io/json/_json.py @@ -59,6 +59,7 @@ IOHandles, _extension_to_compression, dedup_names, + is_potential_multi_index, file_exists, get_handle, is_fsspec_url, @@ -1247,9 +1248,14 @@ def _parse(self) -> None: for k, v in loads(json, precise_float=self.precise_float).items() } self.check_keys_split(decoded) + """ + orig_names = [(tuple(col) if isinstance(col, list) else col) + for col in decoded["columns"]] decoded["columns"] = dedup_names( - names=decoded["columns"], is_potential_multi_index=False + orig_names, + is_potential_multi_index(orig_names, None), ) + """ self.obj = DataFrame(dtype=None, **decoded) elif orient == "index": self.obj = DataFrame.from_dict( diff --git a/pandas/io/parsers/python_parser.py b/pandas/io/parsers/python_parser.py index 59ea8e86f29dd..ec19cbf3c7cfd 100644 --- a/pandas/io/parsers/python_parser.py +++ b/pandas/io/parsers/python_parser.py @@ -263,9 +263,13 @@ def read( columns: Sequence[Hashable] = list(self.orig_names) if not len(content): # pragma: no cover # DataFrame with the right metadata, even though it's length 0 + # error: Cannot determine type of 'index_col' names = dedup_names( self.orig_names, - is_potential_multi_index(self.orig_names, self.index_col), + is_potential_multi_index( + self.orig_names, + self.index_col, # type: ignore[has-type] + ), ) # error: Cannot determine type of 'index_col' index, columns, col_dict = self._get_empty_meta( @@ -300,8 +304,13 @@ def _exclude_implicit_index( self, alldata: list[np.ndarray], ) -> tuple[Mapping[Hashable, np.ndarray], Sequence[Hashable]]: + # error: Cannot determine type of 'index_col' names = dedup_names( - self.orig_names, is_potential_multi_index(self.orig_names, self.index_col) + self.orig_names, + is_potential_multi_index( + self.orig_names, + self.index_col, # type: ignore[has-type] + ), ) offset = 0 diff --git a/pandas/tests/io/json/test_pandas.py b/pandas/tests/io/json/test_pandas.py index 6ee398811ecb1..d254450310dc1 100644 --- a/pandas/tests/io/json/test_pandas.py +++ b/pandas/tests/io/json/test_pandas.py @@ -258,6 +258,27 @@ def test_roundtrip_mixed(self, orient, convert_axes): assert_json_roundtrip_equal(result, expected, orient) + @pytest.mark.xfail( + reason="#50456 Column multiindex is stored and loaded differently" + ) + @pytest.mark.parametrize( + "columns", + [ + [["2022", "2022"], ["JAN", "FEB"]], + [["2022", "2023"], ["JAN", "JAN"]], + [["2022", "2022"], ["JAN", "JAN"]], + ], + ) + def test_roundtrip_multiindex(self, columns): + df = DataFrame( + [[1, 2], [3, 4]], + columns=pd.MultiIndex.from_arrays(columns), + ) + + result = read_json(df.to_json(orient="split"), orient="split") + + tm.assert_frame_equal(result, df) + @pytest.mark.parametrize( "data,msg,orient", [ From 2c59ef826271895de7694d4d8b7c8c9e9f7189e0 Mon Sep 17 00:00:00 2001 From: Marc Garcia Date: Wed, 28 Dec 2022 15:24:55 +0700 Subject: [PATCH 06/10] Restoring commented code --- pandas/io/json/_json.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/io/json/_json.py b/pandas/io/json/_json.py index c9a1e537b8f48..61d91607333d4 100644 --- a/pandas/io/json/_json.py +++ b/pandas/io/json/_json.py @@ -1248,14 +1248,14 @@ def _parse(self) -> None: for k, v in loads(json, precise_float=self.precise_float).items() } self.check_keys_split(decoded) - """ - orig_names = [(tuple(col) if isinstance(col, list) else col) - for col in decoded["columns"]] + orig_names = [ + (tuple(col) if isinstance(col, list) else col) + for col in decoded["columns"] + ] decoded["columns"] = dedup_names( orig_names, is_potential_multi_index(orig_names, None), ) - """ self.obj = DataFrame(dtype=None, **decoded) elif orient == "index": self.obj = DataFrame.from_dict( From a6ea29410cf4d094cc403d1cb21c0efc5269ca70 Mon Sep 17 00:00:00 2001 From: Marc Garcia Date: Wed, 28 Dec 2022 15:35:39 +0700 Subject: [PATCH 07/10] isort --- pandas/io/json/_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/json/_json.py b/pandas/io/json/_json.py index 61d91607333d4..ec2d100ad208c 100644 --- a/pandas/io/json/_json.py +++ b/pandas/io/json/_json.py @@ -59,10 +59,10 @@ IOHandles, _extension_to_compression, dedup_names, - is_potential_multi_index, file_exists, get_handle, is_fsspec_url, + is_potential_multi_index, is_url, stringify_path, ) From 10a9036eb710574142bc8ea718b04c9f186c6ac8 Mon Sep 17 00:00:00 2001 From: Marc Garcia Date: Wed, 28 Dec 2022 16:29:22 +0700 Subject: [PATCH 08/10] Fix doctest --- pandas/io/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/common.py b/pandas/io/common.py index eabae36e5a907..a1c7db68e9893 100644 --- a/pandas/io/common.py +++ b/pandas/io/common.py @@ -1227,7 +1227,7 @@ def dedup_names( Examples -------- - >>> dedup_names(["x", "y", "x", "x"]) + >>> dedup_names(["x", "y", "x", "x"], is_potential_multiindex=False) ['x', 'y', 'x.1', 'x.2'] """ names = list(names) # so we can index From 5a0cd30c97d095a858a27799d07f761943898449 Mon Sep 17 00:00:00 2001 From: Marc Garcia Date: Wed, 28 Dec 2022 22:44:46 +0700 Subject: [PATCH 09/10] Being more specific with xfail --- pandas/tests/io/json/test_pandas.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/tests/io/json/test_pandas.py b/pandas/tests/io/json/test_pandas.py index b3193da8d9292..f37c7dcec0f0a 100644 --- a/pandas/tests/io/json/test_pandas.py +++ b/pandas/tests/io/json/test_pandas.py @@ -259,7 +259,8 @@ def test_roundtrip_mixed(self, orient, convert_axes): assert_json_roundtrip_equal(result, expected, orient) @pytest.mark.xfail( - reason="#50456 Column multiindex is stored and loaded differently" + reason="#50456 Column multiindex is stored and loaded differently", + raises=AssertionError, ) @pytest.mark.parametrize( "columns", From 8bad27cb770f0c1a51f82805a99c6329e72ca08d Mon Sep 17 00:00:00 2001 From: Marc Garcia Date: Thu, 29 Dec 2022 00:37:10 +0700 Subject: [PATCH 10/10] Update whatsnew issue number --- doc/source/whatsnew/v2.0.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index 6ac8b007c8247..1288f00b39efd 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -496,7 +496,7 @@ Other API changes new DataFrame (shallow copy) instead of the original DataFrame, consistent with other methods to get a full slice (for example ``df.loc[:]`` or ``df[:]``) (:issue:`49469`) - Disallow computing ``cumprod`` for :class:`Timedelta` object; previously this returned incorrect values (:issue:`50246`) -- Loading a JSON file with duplicate columns using ``read_json(orient='split')`` renames columns to avoid duplicates, as :func:`read_csv` and the other readers do (:issue:`XXX`) +- Loading a JSON file with duplicate columns using ``read_json(orient='split')`` renames columns to avoid duplicates, as :func:`read_csv` and the other readers do (:issue:`50370`) .. --------------------------------------------------------------------------- .. _whatsnew_200.deprecations: