Skip to content

Commit 1dbbcc9

Browse files
committed
feat: add shift method to Series
Implements Series.shift(periods, options) matching the pandas API. Supports positive/negative periods and custom fillValue. Includes tests for all edge cases. Fixes #617
1 parent 21d8c55 commit 1dbbcc9

File tree

2 files changed

+90
-1
lines changed

2 files changed

+90
-1
lines changed

src/danfojs-base/core/series.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,61 @@ export default class Series extends NDframe implements SeriesInterface {
192192
return this.iloc([`${startIdx}:`])
193193
}
194194

195+
/**
196+
* Shift index by desired number of periods. Shifted data is filled with NaN (or fillValue).
197+
* @param periods Number of periods to shift. Positive shifts down, negative shifts up.
198+
* @param options Optional. An object with a `fillValue` property for filling shifted positions.
199+
* @example
200+
* ```
201+
* const sf = new Series([1, 2, 3, 4, 5]);
202+
* const sf2 = sf.shift(1);
203+
* console.log(sf2.values); // [NaN, 1, 2, 3, 4]
204+
* ```
205+
* @example
206+
* ```
207+
* const sf = new Series([1, 2, 3, 4, 5]);
208+
* const sf2 = sf.shift(-1);
209+
* console.log(sf2.values); // [2, 3, 4, 5, NaN]
210+
* ```
211+
*/
212+
shift(periods: number = 1, options?: { fillValue?: number | string | boolean }): Series {
213+
const fillValue = options?.fillValue !== undefined ? options.fillValue : NaN;
214+
const values = this.values as ArrayType1D;
215+
const len = values.length;
216+
const newValues: ArrayType1D = new Array(len).fill(fillValue);
217+
218+
if (periods === 0) {
219+
return this.copy();
220+
}
221+
222+
if (Math.abs(periods) >= len) {
223+
return new Series(newValues, {
224+
index: [...this.index],
225+
columns: this.columns,
226+
dtypes: this.dtypes,
227+
config: { ...this.config }
228+
});
229+
}
230+
231+
if (periods > 0) {
232+
for (let i = periods; i < len; i++) {
233+
newValues[i] = values[i - periods];
234+
}
235+
} else {
236+
const absPeriods = Math.abs(periods);
237+
for (let i = 0; i < len - absPeriods; i++) {
238+
newValues[i] = values[i + absPeriods];
239+
}
240+
}
241+
242+
return new Series(newValues, {
243+
index: [...this.index],
244+
columns: this.columns,
245+
dtypes: this.dtypes,
246+
config: { ...this.config }
247+
});
248+
}
249+
195250
/**
196251
* Returns specified number of random rows in a Series
197252
* @param num The number of rows to return

src/danfojs-node/test/core/series.test.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1755,4 +1755,38 @@ describe("Series Functions", () => {
17551755
});
17561756

17571757
});
1758-
})
1758+
1759+
describe("shift", function () {
1760+
it("Shifts values down by 1 with NaN fill", function () {
1761+
const sf = new Series([1, 2, 3, 4, 5]);
1762+
const result = sf.shift(1);
1763+
assert.deepEqual(result.values[0], NaN);
1764+
assert.deepEqual(result.values.slice(1), [1, 2, 3, 4]);
1765+
});
1766+
1767+
it("Shifts values up by -1 with NaN fill", function () {
1768+
const sf = new Series([1, 2, 3, 4, 5]);
1769+
const result = sf.shift(-1);
1770+
assert.deepEqual(result.values.slice(0, 4), [2, 3, 4, 5]);
1771+
assert.deepEqual(result.values[4], NaN);
1772+
});
1773+
1774+
it("Returns copy when periods is 0", function () {
1775+
const sf = new Series([1, 2, 3]);
1776+
const result = sf.shift(0);
1777+
assert.deepEqual(result.values, sf.values);
1778+
});
1779+
1780+
it("Uses custom fillValue", function () {
1781+
const sf = new Series([1, 2, 3, 4]);
1782+
const result = sf.shift(2, { fillValue: 0 });
1783+
assert.deepEqual(result.values, [0, 0, 1, 2]);
1784+
});
1785+
1786+
it("Returns all fill values when shift exceeds length", function () {
1787+
const sf = new Series([1, 2, 3]);
1788+
const result = sf.shift(5);
1789+
assert.ok(result.values.every((v: any) => isNaN(v)));
1790+
});
1791+
});
1792+
})

0 commit comments

Comments
 (0)