Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion pynapple/core/interval_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,9 @@ def __init__(
start = start.start.astype(np.float64)

elif isinstance(start, pd.DataFrame):
assert "start" in start.columns and "end" in start.columns, """
assert (
"start" in start.columns and "end" in start.columns
), """
DataFrame must contain columns name "start" and "end" for start and end times.
"""
# try sorting the DataFrame by start times, preserving its end pair, as an effort to preserve metadata
Expand Down Expand Up @@ -468,6 +470,33 @@ def __getitem__(self, key):
# only works for list of metadata columns
return _MetadataMixin.__getitem__(self, key)

# A separate if-elif block to reorder key if given row style index of type
# list[int] or slice that is not in ascending order.
if isinstance(key, list) and all(isinstance(x, int) for x in key):
Comment on lines +473 to +475
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description mentions potentially adjusting .loc[tuple] behavior for a tuple of (row_index, column_keys), but this change only normalizes row-style list/slice keys in IntervalSet.__getitem__ and doesn’t affect IntervalSet.loc (implemented via _IntervalSetSliceHelper). If .loc[(rows, [cols...])] support is intended to be part of this PR, it looks missing here.

Copilot uses AI. Check for mistakes.
# check if list is sorted (ascending) and not duplicated.
if not all(x < y for x, y in zip(key, key[1:])):
key = sorted(list(set(key)))
warnings.warn(
"Received an unsorted or duplicate index, this is sorted to preserve the invariant that "
"nap.IntervalSet remains ordered. This differs from standard NumPy/Pandas "
"indexing semantics as index order is not preserved.",
UserWarning,
)

elif isinstance(key, slice):
if key.step is None or key.step > 0:
pass # positive or defautl step no action needed
else:
# if slice is descending, compute the actual index and reorder it
# to be in ascending order.
key = sorted([i for i in range(*key.indices(self.shape[0]))])
warnings.warn(
"Received descending slice, this is reversed to preserve the invariant that "
"nap.IntervalSet remains ordered. This differs from standard NumPy/Pandas "
"indexing semantics as index order is not preserved.",
UserWarning,
)

if isinstance(key, tuple):
if len(key) == 2:
# any 2D indexing will only act on start and end values
Expand Down
4 changes: 3 additions & 1 deletion pynapple/io/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ def get_error_text(path):

A more advanced project for creating NWB files is neuroconv:
https://neuroconv.readthedocs.io/en/main/
""".format(path)
""".format(
path
)

error_txt = "\n" + border + "\n" + txt1 + "\n" + border
return error_txt
Expand Down
32 changes: 32 additions & 0 deletions tests/test_interval_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,38 @@ def test_get_iset():
)


def test_get_iset_non_ascending():
# Get new ivset with unsorted/descending indices
# Needed a slightly longer test case with metadat for this.
# So I maded a separate test block
start = np.array([0, 10, 16, 21, 26], dtype=np.float64)
end = np.array([5, 15, 20, 25, 30], dtype=np.float64)
metadata = pd.DataFrame(
{
"label": ["a", "b", "c", "d", "e"],
"score": [1, 2, 3, 4, 5],
}
)
ep = nap.IntervalSet(start=start, end=end, metadata=metadata)
with pytest.warns(UserWarning, match="descending slice"):
ep2 = ep[::-2]
assert isinstance(ep2, nap.IntervalSet)
expected_values = ep.values[::-2][::-1]
np.testing.assert_array_almost_equal(ep2.values, expected_values)
expected_metadata = metadata.iloc[::-2].iloc[::-1].reset_index(drop=True)
pd.testing.assert_frame_equal(ep2.metadata, expected_metadata)

idx = [0, 4, 4, 2]
with pytest.warns(UserWarning, match="unsorted or duplicate index"):
ep2 = ep[idx]
assert isinstance(ep2, nap.IntervalSet)
expected_indices = sorted(list(set(idx)))
expected_values = ep.values[expected_indices]
np.testing.assert_array_almost_equal(ep2.values, expected_values)
expected_metadata = metadata.iloc[expected_indices].reset_index(drop=True)
pd.testing.assert_frame_equal(ep2.metadata, expected_metadata)


def test_get_iset_with_series():
start = np.array([0, 10, 16], dtype=np.float64)
end = np.array([5, 15, 20], dtype=np.float64)
Expand Down
Loading