Skip to content

Commit 7c778cb

Browse files
committed
[Feat] Explode dataframe columns with list values
- Add _explode_dataframe() to flatten metric columns into rows - Fix metric error message formatting Signed-off-by: ConvolutedDog <yangjianchao16@nudt.edu.cn>
1 parent 257acee commit 7c778cb

File tree

8 files changed

+52
-28
lines changed

8 files changed

+52
-28
lines changed

examples/08_multiple_metrics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def main() -> None:
5151
df = results.to_dataframe()
5252
print(df)
5353

54-
unique_metrics = df["Metric"].unique()[0]
54+
unique_metrics = df["Metric"].unique()
5555
print(f"\n✓ Collected {len(unique_metrics)} metrics:")
5656
for metric in unique_metrics:
5757
print(f" - {metric}")

nsight/collection/core.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from collections.abc import Callable, Collection, Iterable, Sequence
1414
from typing import Any
1515

16+
import numpy as np
1617
import pandas as pd
1718

1819
from nsight import annotation, exceptions, thermovision, transformation, utils
@@ -435,6 +436,10 @@ def wrapper(
435436
self.settings.output_progress,
436437
)
437438

439+
# Explode the dataframe.
440+
raw_df = self._explode_dataframe(raw_df)
441+
processed = self._explode_dataframe(processed)
442+
438443
# Save to CSV if enabled
439444
if self.settings.output_csv:
440445
raw_csv_path = (
@@ -468,3 +473,42 @@ def wrapper(
468473
return None
469474

470475
return wrapper
476+
477+
def _explode_dataframe(self, df: pd.DataFrame) -> pd.DataFrame:
478+
"""
479+
Explode columns with list/tuple/np.ndarray values into multiple rows.
480+
481+
Two scenarios:
482+
1. No derived metrics (all "Transformed" = False):
483+
- All columns maybe contain multiple values (lists/arrays).
484+
- Use `explode()` to flatten each list element into separate rows.
485+
2. With derived metrics:
486+
- Metric columns contain either:
487+
a) Single-element lists (from derived metrics) - extract the scalar
488+
b) Scalars (from original metrics) - keep as-is
489+
- Only flatten single-element lists to scalars, don't create new rows.
490+
491+
Args:
492+
df: Dataframe to be exploded.
493+
494+
Returns:
495+
Exploded dataframe.
496+
"""
497+
df_explode = None
498+
if df["Transformed"].eq(False).all():
499+
# 1: No derived metrics - explode all columns with sequences into rows.
500+
df_explode = df.apply(pd.Series.explode).reset_index(drop=True)
501+
else:
502+
# 2: With derived metrics - only explode columns with single-value sequences.
503+
df_explode = df.apply(
504+
lambda col: (
505+
col.apply(
506+
lambda x: (
507+
x[0]
508+
if isinstance(x, (list, tuple, np.ndarray)) and len(x) == 1
509+
else x
510+
)
511+
)
512+
)
513+
)
514+
return df_explode

nsight/collection/ncu.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,10 +245,6 @@ def collect(
245245
# If NSPY_NCU_PROFILE is set, just run the function normally
246246
name = os.environ["NSPY_NCU_PROFILE"]
247247

248-
# TODO: If we have two functions to profile in one script, we cannot access
249-
# the result of the first function. Because when we profile the second function,
250-
# the first function will return None.
251-
252248
# If this is not the function we are profiling, stop
253249
if func.__name__ != name:
254250
return None

nsight/exceptions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class NCUNotAvailableError(Exception):
4646
CUDA_CORE_UNAVAILABLE_MSG = "cuda-core is required for ignore_failures functionality.\n Install it with:\n - pip install nsight-python[cu12] (if you have CUDA 12.x)\n - pip install nsight-python[cu13] (if you have CUDA 13.x)"
4747

4848

49-
def get_metric_error_message(
49+
def get_metrics_error_message(
5050
metrics: Sequence[str], error_type: MetricErrorType
5151
) -> str:
5252
"""
@@ -60,7 +60,7 @@ def get_metric_error_message(
6060
str: User-friendly error message with guidance.
6161
"""
6262
return (
63-
f"{error_type.value} value '{metrics}' for 'metrics' parameter for nsight.analyze.kernel(). "
63+
f"{error_type.value} value {metrics} for 'metrics' parameter for nsight.analyze.kernel()."
6464
f"\nPlease refer ncu --query-metrics for list of supported metrics."
6565
)
6666

nsight/extraction.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def extract_ncu_action_data(action: Any, metrics: Sequence[str]) -> utils.NCUAct
4343
"""
4444
for metric in metrics:
4545
if metric not in action.metric_names():
46-
error_message = exceptions.get_metric_error_message(
46+
error_message = exceptions.get_metrics_error_message(
4747
metric, error_type=exceptions.MetricErrorType.INVALID
4848
)
4949
raise exceptions.ProfilerException(error_message)
@@ -206,7 +206,6 @@ def extract_df_from_report(
206206
# evaluate the measured metrics
207207
values = data.values
208208
if derive_metrics is not None:
209-
# TODO: Add support for multiple derived metrics.
210209
derived_metrics: float | int | None = (
211210
None if values is None else derive_metrics(*values, *conf)
212211
)

nsight/transformation.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -224,17 +224,4 @@ def compute_group_geomean(valid_values: pd.Series) -> Any:
224224
# Add geomean values to the DataFrame
225225
agg_df["Geomean"] = agg_df["Annotation"].map(geomean_values)
226226

227-
# If the column has only one value, and it's a list/tuple/np.ndarray, flatten it.
228-
agg_df = agg_df.apply(
229-
lambda col: (
230-
col.apply(
231-
lambda x: (
232-
x[0]
233-
if isinstance(x, (list, tuple, np.ndarray)) and len(x) == 1
234-
else x
235-
)
236-
)
237-
)
238-
)
239-
240227
return agg_df

nsight/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
CUDA_CORE_UNAVAILABLE_MSG,
2020
MetricErrorType,
2121
NCUErrorContext,
22-
get_metric_error_message,
22+
get_metrics_error_message,
2323
)
2424

2525
# Try to import cuda-core (optional dependency)
@@ -321,7 +321,7 @@ def format_ncu_error_message(context: NCUErrorContext) -> str:
321321

322322
if context.errors and INVALID_METRIC_ERROR_HINT in context.errors[0]:
323323
message_parts.append(
324-
get_metric_error_message(
324+
get_metrics_error_message(
325325
context.metrics, error_type=MetricErrorType.INVALID
326326
)
327327
)

tests/test_profiler.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
"""
77

88
import os
9-
import re
109
import shutil
11-
import tempfile
1210
from collections.abc import Generator
1311
from typing import Any, Literal
1412

@@ -953,8 +951,8 @@ def profiled_func(x: int, y: int) -> None:
953951
if metric == "invalid_value":
954952
with pytest.raises(
955953
exceptions.ProfilerException,
956-
match=re.escape(
957-
f"Invalid value '['{metric}']' for 'metric' parameter for nsight.analyze.kernel()"
954+
match=(
955+
rf"Invalid value \['{metric}'\] for 'metrics' parameter for nsight.analyze.kernel()"
958956
),
959957
):
960958
profiled_func()

0 commit comments

Comments
 (0)