-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Implement Series.unstack #24005
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
base: main
Are you sure you want to change the base?
Implement Series.unstack #24005
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1154,6 +1154,59 @@ def to_frame(self, name: Hashable = no_default) -> DataFrame: | |
| self._propagate_metadata(res) | ||
| return res | ||
|
|
||
| @_performance_tracking | ||
| def unstack(self, level=-1, fill_value=None, sort: bool = True): | ||
| """ | ||
| Unstack, also known as pivot, Series with MultiIndex to produce | ||
| DataFrame. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| level : int, str, or list of these, default last level | ||
| Level(s) to unstack, can pass level name. | ||
| fill_value | ||
| Non-functional argument provided for compatibility with Pandas. | ||
| sort : bool, default True | ||
| Sort the level(s) in the resulting MultiIndex columns. | ||
|
|
||
| Returns | ||
| ------- | ||
| DataFrame | ||
| Unstacked Series. | ||
|
|
||
| Examples | ||
| -------- | ||
| >>> import cudf | ||
| >>> s = cudf.Series( | ||
| ... [1, 2, 3, 4], | ||
| ... index=cudf.MultiIndex.from_product([["one", "two"], ["a", "b"]]), | ||
| ... ) | ||
| >>> s | ||
| one a 1 | ||
| b 2 | ||
| two a 3 | ||
| b 4 | ||
| dtype: int64 | ||
| >>> s.unstack(level=-1) | ||
| a b | ||
| one 1 2 | ||
| two 3 4 | ||
| """ | ||
| if not isinstance(self.index, cudf.MultiIndex): | ||
| raise ValueError( | ||
| "index must be a MultiIndex to unstack, " | ||
| f"{type(self.index)} was passed" | ||
| ) | ||
| result = self.to_frame().unstack( | ||
| level=level, fill_value=fill_value, sort=sort | ||
| ) | ||
| if result.columns.nlevels == 1: | ||
| # No level was actually unstacked (e.g. level=[]); pandas | ||
| # returns the original Series unchanged in that case. | ||
| return self.copy(deep=False) | ||
| result.columns = result.columns.droplevel(0) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🤖 get_repo_knowledge executed:
Length of output: 16091 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- changed method ---'
sed -n '1125,1220p' python/cudf/cudf/core/series.py
printf '%s\n' '--- DataFrame.unstack definitions and tests ---'
rg -n -U -A35 -B10 'def unstack|unstack\(.*level' python/cudf/cudf python/cudf/tests | head -n 500Repository: NVIDIA/cudf Length of output: 27510 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- unstack implementation ---'
sed -n '1338,1475p' python/cudf/cudf/core/reshape.py
printf '%s\n' '--- Series/DataFrame unstack tests ---'
sed -n '1,155p' python/cudf/cudf/tests/reshape/test_unstack.py
printf '%s\n' '--- relevant MultiIndex droplevel implementation ---'
rg -n -U -A35 -B8 'def droplevel' python/cudf/cudf/coreRepository: NVIDIA/cudf Length of output: 15990 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- Index.droplevel binding ---'
rg -n -U -A45 -B8 'def droplevel' python/cudf/cudf/core/index.py python/cudf/cudf/core/multiindex.py
printf '%s\n' '--- DataFrame/Series frame construction bindings ---'
rg -n -U -A30 -B8 'def to_frame|def _to_frame' python/cudf/cudf/core/series.py
printf '%s\n' '--- available pandas version and empty-level behavior ---'
python3 - <<'PY'
try:
import pandas as pd
except Exception as exc:
print(f"pandas unavailable: {type(exc).__name__}: {exc}")
else:
print(f"pandas={pd.__version__}")
idx = pd.MultiIndex.from_product([["one", "two"], ["a", "b"]])
s = pd.Series([1, 2, 3, 4], index=idx, name="value")
for level in ([], ()):
try:
result = s.unstack(level=level)
print(
f"level={level!r}: type={type(result).__name__}, "
f"shape={result.shape}, columns={result.columns!r}"
)
except Exception as exc:
print(f"level={level!r}: {type(exc).__name__}: {exc}")
PYRepository: NVIDIA/cudf Length of output: 5477 🌐 Web query:
💡 Result: In pandas, the unstack method accepts an integer, string, or a list of these as the level parameter to specify which index level(s) to unstack [1][2]. When an empty list ([]) is passed to the level parameter (e.g., s.unstack(level=[])), pandas raises a ValueError. This behavior occurs because the unstack operation expects at least one valid level to pivot from the index axis to the column axis [1][2]. Providing an empty list fails to identify any index level for the transformation, resulting in an error indicating that the level could not be found or is invalid. To unstack a Series or DataFrame, you must provide either the default level (which is the last level, level=-1) or explicitly specify one or more valid level identifiers [1][2]. Citations:
🌐 Web query:
💡 Result: In pandas, passing an empty list ( Citations:
🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- complete Index implementation candidates ---'
rg -n -U -A35 -B8 'class Index|def droplevel|droplevel\(' python/cudf/cudf/core/index.py python/cudf/cudf/core/indexed_frame.py python/cudf/cudf/core/column_accessor.py | head -n 300
printf '%s\n' '--- Series unstack references ---'
rg -n -A12 -B8 'series_unstack|Series.*unstack|unstack\(level=\[\]|level=\[\]' python/cudf/cudf/tests python/cudf/cudf/core | head -n 300Repository: NVIDIA/cudf Length of output: 12320 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- DataFrame columns property and Index droplevel symbols ---'
rg -n -U -A28 -B8 'def columns|@.*columns|droplevel' python/cudf/cudf/core/dataframe.py python/cudf/cudf/core/index.py python/cudf/cudf/core/indexed_frame.py | head -n 350
printf '%s\n' '--- pandas source for empty level validation ---'
python3 - <<'PY'
from urllib.request import urlopen
urls = [
"https://raw.githubusercontent.com/pandas-dev/pandas/v2.2.3/pandas/core/reshape/reshape.py",
"https://raw.githubusercontent.com/pandas-dev/pandas/main/pandas/core/reshape/reshape.py",
]
for url in urls:
try:
text = urlopen(url, timeout=10).read().decode()
except Exception as exc:
print(f"{url}: unavailable: {type(exc).__name__}: {exc}")
continue
print(f"--- {url} ---")
lines = text.splitlines()
for i, line in enumerate(lines):
if "def _unstack" in line or "level" in line and "len" in line:
start = max(0, i - 4)
end = min(len(lines), i + 18)
print("\n".join(f"{j+1}: {lines[j]}" for j in range(start, end)))
PYRepository: NVIDIA/cudf Length of output: 4193 🏁 Script executed: #!/bin/bash
set -eu
url='https://raw.githubusercontent.com/pandas-dev/pandas/v2.2.3/pandas/core/reshape/reshape.py'
curl -ksSf "$url" | grep -n -A35 -B8 'class _Unstacker'Repository: NVIDIA/cudf Length of output: 1606 🏁 Script executed: #!/bin/bash
set -eu
curl -ksSf 'https://raw.githubusercontent.com/pandas-dev/pandas/v2.2.3/pandas/core/reshape/reshape.py' |
grep -n -A80 -B8 -E 'def __init__|level = _get_level_number|level.*list|len\(level\)' | head -n 220Repository: NVIDIA/cudf Length of output: 8842 🏁 Script executed: #!/bin/bash
set -eu
curl -ksSf 'https://raw.githubusercontent.com/pandas-dev/pandas/v2.2.3/pandas/core/indexes/base.py' |
grep -n -A45 -B8 'def droplevel'Repository: NVIDIA/cudf Length of output: 1948 🏁 Script executed: #!/bin/bash
set -eu
curl -ksSf 'https://raw.githubusercontent.com/pandas-dev/pandas/v2.2.3/pandas/core/indexes/base.py' |
sed -n '2148,2185p'Repository: NVIDIA/cudf Length of output: 1333 Preserve empty level-list semantics. When 🤖 Prompt for AI Agents |
||
| return result | ||
|
|
||
| @_performance_tracking | ||
| def memory_usage(self, index: bool = True, deep: bool = False) -> int: | ||
| """ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🤖 get_repo_knowledge executed:
get_repo_knowledge NVIDIA/cudf /tmp/coderabbit-repo-knowledge/nvidia-cudf-5d380a13/architecture /tmp/coderabbit-repo-knowledge/nvidia-cudf-5d380a13/conventions /tmp/coderabbit-repo-knowledge/nvidia-cudf-5d380a13/learningsLength of output: 46838
🏁 Script executed:
Repository: NVIDIA/cudf
Length of output: 6718
🏁 Script executed:
Repository: NVIDIA/cudf
Length of output: 13474
🏁 Script executed:
Repository: NVIDIA/cudf
Length of output: 215
🏁 Script executed:
Repository: NVIDIA/cudf
Length of output: 23233
🌐 Web query:
pandas Series.unstack empty list level tuple-valued Series name behavior💡 Result:
In pandas, the behavior of
Series.unstackwhen passing an empty list[]to thelevelparameter, or dealing with tuple-valued index names, is defined by how pandas interprets iterables within that parameter [1][2]. When thelevelparameter is passed a list (e.g.,level=[]), pandas expects to unstack multiple levels simultaneously [1][2]. Passing an empty list[]toleveleffectively requests to unstack zero levels, which typically results in the original Series being returned unchanged, though in some versions or edge cases, it may raise an error depending on the internal implementation of_unstack_multiple[3][4]. Regarding tuple-valued level names, pandas historically faced challenges where passing a tuple as a level name (e.g.,s.unstack(('level_name_part1', 'level_name_part2'))) would cause aKeyError[3]. This occurred because pandas' internal logic forunstackoften assumes that any iterable passed tolevelmust refer to multiple index levels, rather than a single level whose name happens to be a tuple [3]. If you encounter issues with tuple-valued names, it is recommended to use the integer position of the level or ensure your index names are not ambiguous [3]. For complex or empty-input scenarios, developers have sometimes implemented workarounds—such as custom functions—to ensure consistent output schemas, particularly when dealing with empty DataFrames or specific MultiIndex structures where standardunstackbehavior may drop columns or fail to produce the desired output [5][6]. Top results: [1], [5], [2], [3]Citations:
🏁 Script executed:
Repository: NVIDIA/cudf
Length of output: 19510
🌐 Web query:
site:github.com/pandas-dev/pandas pandas Series.unstack level=[] returns Series unchanged source💡 Result:
In pandas, calling
Series.unstack(level=[])with an empty list as thelevelargument typically results in a no-op, returning the original Series unchanged [1]. Theunstackmethod is designed to reshape a Series with a MultiIndex into a DataFrame by pivoting one or more levels [2][3][4]. When a list of levels is provided tolevel, pandas iterates through those levels to perform the unstacking operation [5][6]. If the list provided is empty, the operation essentially finds no levels to unstack, meaning no reshaping occurs, and the method returns the original object [1]. This behavior is consistent with similar operations in the library, where passing an empty collection to a parameter that expects levels to process is treated as a request to perform no action [1]. Users should be aware that while this is the standard expectation for a no-op, internal implementations (such as thefuture_stackwork instack) have occasionally encountered bugs where empty lists were incorrectly processed or raised errors; however, for the standardunstackmethod, an empty list is intended to return the Series unchanged [1].Citations:
Detect the no-op from
level, not from column depth.When
levelis empty, return the originalSeriesbeforeto_frame().unstack().Series.to_frame()preserves a tuple-valued name as a multi-level column label, so the current guard can drop column level 0 and return aDataFrameinstead. Add regression coverage for tuple-valued names and empty list-likelevelvalues.🤖 Prompt for AI Agents
Source: Coding guidelines