diff --git a/src/danfojs-base/core/frame.ts b/src/danfojs-base/core/frame.ts index 4b64ce7f..04bec0b9 100644 --- a/src/danfojs-base/core/frame.ts +++ b/src/danfojs-base/core/frame.ts @@ -329,6 +329,9 @@ export default class DataFrame extends NDframe implements DataFrameInterface { case 'div': tensorResult = tensors[0].div(tensors[1]) break; + case 'divNoNan': + tensorResult = tensors[0].divNoNan(tensors[1]) + break; case 'mul': tensorResult = tensors[0].mul(tensors[1]) break; @@ -726,6 +729,36 @@ export default class DataFrame extends NDframe implements DataFrameInterface { return this.$MathOps(tensors, "div", inplace) + } + + /** + * Return division of DataFrame with other, returns 0 if denominator is 0. + * @param other DataFrame, Series, Array or Scalar number to divide with. + * @param options.axis 0 or 1. If 0, compute the division column-wise, if 1, row-wise + * @param options.inplace Boolean indicating whether to perform the operation inplace or not. Defaults to false + * @example + * ``` + * const df = new DataFrame([[1, 2], [3, 4]], { columns: ['A', 'B'] }) + * const df2 = df.divNoNan(2) + * df2.print() + * ``` + */ + divNoNan(other: DataFrame | Series | number[] | number, options?: { axis?: 0 | 1, inplace?: boolean }): DataFrame + divNoNan(other: DataFrame | Series | number[] | number, options?: { axis?: 0 | 1, inplace?: boolean }): DataFrame | void { + const { inplace, axis } = { inplace: false, axis: 1, ...options } + + if (this.$frameIsNotCompactibleForArithmeticOperation()) { + throw Error("TypeError: div operation is not supported for string dtypes"); + } + + if ([0, 1].indexOf(axis) === -1) { + throw Error("ParamError: Axis must be 0 or 1"); + } + + const tensors = this.$getTensorsForArithmeticOperationByAxis(other, axis); + return this.$MathOps(tensors, "divNoNan", inplace) + + } /** @@ -1333,6 +1366,87 @@ export default class DataFrame extends NDframe implements DataFrameInterface { } + /** + * Return percentage difference of DataFrame with other. + * @param other DataFrame, Series, Array or Scalar number (positive numbers are preceding rows, negative are following rows) to compare difference with. + * @param options.axis 0 or 1. If 0, compute the difference column-wise, if 1, row-wise + * @param options.inplace Boolean indicating whether to perform the operation inplace or not. Defaults to false + * @example + * ``` + * const df = new DataFrame([[1, 2, 3, 4, 5, 6], [1, 1, 2, 3, 5, 8], [1, 4, 9, 16, 25, 36]], { columns: ['A', 'B', 'C'] }) + * + * // Percentage difference with previous row + * const df0 = df.pctChange(1) + * console.log(df0) + * + * // Percentage difference with previous column + * const df1 = df.pctChange(1, {axis: 0}) + * console.log(df1) + * + * // Percentage difference with previous 3rd previous row + * const df2 = df.pctChange(3) + * console.log(df2) + * + * // Percentage difference with following row + * const df3 = df.pctChange(-1) + * console.log(df3) + * + * // Percentage difference with another DataFrame + * const df4 = df.pctChange(df3) + * console.log(df4) + * ``` + */ + pctChange(other: DataFrame | Series | number[] | number, options?: { axis?: 0 | 1, inplace?: boolean }): DataFrame + pctChange(other: DataFrame | Series | number[] | number, options?: { axis?: 0 | 1, inplace?: boolean }): DataFrame | void { + const { inplace, axis } = { inplace: false, axis: 1, ...options } + + if (this.$frameIsNotCompactibleForArithmeticOperation()) { + throw Error("TypeError: pctChange operation is not supported for string dtypes"); + } + + if ([0, 1].indexOf(axis) === -1) { + throw Error("ParamError: Axis must be 0 or 1"); + } + + if (other === 0) { + return this; + } + + if (typeof other === "number") { + let origDF = this.copy() as DataFrame; + if (axis === 0) { + origDF = origDF.T; + } + const originalTensor = origDF.tensor.clone(); + const unit = new Array(originalTensor.shape[originalTensor.rank - 1]).fill(NaN); + let pctArray: any[] = originalTensor.arraySync(); + if (other > 0) { + for (let i = 0; i < other; i++) { + pctArray.unshift(unit); + pctArray.pop(); + } + } + else if (other < 0) { + for (let i = 0; i > other; i--) { + pctArray.push(unit); + pctArray.shift(); + } + } + const pctTensor = tensorflow.tensor2d(pctArray, originalTensor.shape); + const pctDF = (this.$MathOps([originalTensor, pctTensor], "divNoNan", inplace) as DataFrame).sub(1); + if (axis === 0) { + return pctDF.T; + } + return pctDF; + } + + if (other instanceof DataFrame || other instanceof Series) { + const tensors = this.$getTensorsForArithmeticOperationByAxis(other, axis); + const pctDF = (this.$MathOps(tensors, "divNoNan", inplace) as DataFrame).sub(1); + return pctDF; + } + } + /** * Return difference of DataFrame with other. * @param other DataFrame, Series, Array or Scalar number (positive numbers are preceding rows, negative are following rows) to compare difference with. diff --git a/src/danfojs-base/shared/types.ts b/src/danfojs-base/shared/types.ts index 3b7a8201..b62894b0 100644 --- a/src/danfojs-base/shared/types.ts +++ b/src/danfojs-base/shared/types.ts @@ -216,8 +216,10 @@ export interface DataFrameInterface extends NDframeInterface { sub(other: DataFrame | Series | number | number[], options?: { axis?: 0 | 1, inplace?: boolean }): DataFrame | void mul(other: DataFrame | Series | number | number[], options?: { axis?: 0 | 1, inplace?: boolean }): DataFrame | void div(other: DataFrame | Series | number | number[], options?: { axis?: 0 | 1, inplace?: boolean }): DataFrame | void + divNoNan(other: DataFrame | Series | number | number[], options?: { axis?: 0 | 1, inplace?: boolean }): DataFrame | void pow(other: DataFrame | Series | number | number[], options?: { axis?: 0 | 1, inplace?: boolean }): DataFrame | void mod(other: DataFrame | Series | number | number[], options?: { axis?: 0 | 1, inplace?: boolean }): DataFrame | void + pctChange(other: DataFrame | Series | number[] | number, options?: { axis?: 0 | 1, inplace?: boolean }): DataFrame | void diff(other: DataFrame | Series | number[] | number, options?: { axis?: 0 | 1, inplace?: boolean }): DataFrame | void mean(options?: { axis?: 0 | 1 }): Series median(options?: { axis?: 0 | 1 }): Series diff --git a/src/danfojs-browser/tests/core/frame.test.js b/src/danfojs-browser/tests/core/frame.test.js index 90c5121e..f6978383 100644 --- a/src/danfojs-browser/tests/core/frame.test.js +++ b/src/danfojs-browser/tests/core/frame.test.js @@ -1018,6 +1018,51 @@ describe("DataFrame", function () { }); + describe("pctChange", function () { + it("Return same DataFrame if other === 0", function () { + const data = [ [ 0, 2, 4 ], [ 10, 10, 10 ], [ 1, 2, 3 ] ]; + const df = new dfd.DataFrame(data); + assert.deepEqual((df.pctChange(0)).values, [ [ 0, 2, 4 ], [ 10, 10, 10 ], [ 1, 2, 3 ] ]); + }); + it("Return difference in percentage of DataFrame with previous row", function () { + const data = [ [ 90 ], [ 900 ], [ 900 ] ]; + const df = new dfd.DataFrame(data); + assert.deepEqual((df.pctChange(1)).values, [ [ NaN ], [ 9 ], [ 0 ] ]); + }); + it("Return difference in percentage of DataFrame with following row", function () { + const data = [ [ 0, 5, 15 ], [ 10, 10, 10 ], [ 1, 2, 5 ] ]; + const df = new dfd.DataFrame(data); + assert.deepEqual((df.pctChange(-1)).values, [ [ -1, -0.5, 0.5 ], [ 9, 4, 1 ], [ NaN, NaN, NaN ] ]); + }); + it("Return difference in percentage of a DataFrame with a Series along default axis 1", function () { + const data = [ [ 0, 2, 4 ], [ 10, 10, 10 ], [ 1, 2, 3 ] ]; + const sf = new dfd.Series([ 1, 2, 1 ]); + const df = new dfd.DataFrame(data); + assert.deepEqual((df.pctChange(sf)).values, [ [ -1, 0, 3 ], [ 9, 4, 9 ], [ 0, 0, 2 ] ]); + }); + it("Return difference in percentage of a DataFrame with along axis 0 (column-wise), previous column", function () { + const data = [ [ 0, 2, 4 ], [ 10, 10, 10 ], [ 1, 2, 3 ] ]; + const df = new dfd.DataFrame(data); + assert.deepEqual((df.pctChange(1, { axis: 0 })).values, [ [ NaN, -1, 1 ], [ NaN, 0, 0 ], [ NaN, 1, 0.5 ] ]); + }); + it("Return difference in percentage of a DataFrame with along axis 0 (column-wise), following column", function () { + const data = [ [ 0, 2, 4 ], [ 10, 10, 10 ], [ 1, 2, 8 ] ]; + const df = new dfd.DataFrame(data); + assert.deepEqual((df.pctChange(-1, { axis: 0 })).values, [ [ -1, -0.5, NaN ], [ 0, 0, NaN ], [ -0.5, -0.75, NaN ] ]); + }); + it("Return difference in percentage of a DataFrame with another DataFrame along default axis 1", function () { + const df1 = new dfd.DataFrame([ [ 0, 2, 4 ], [ 3, 10, 4 ] ]); + const df2 = new dfd.DataFrame([ [ -1, -2, 4 ], [ 6, 5, 0 ] ]); + assert.deepEqual((df1.pctChange(df2)).values, [ [ -1, -2, 0 ], [ -0.5, 1, -1 ] ]); + }); + it("Throw error if DataFrame for pctChange contains string", function () { + const df = new dfd.DataFrame([ [ "words", "words", "words" ], [ "words", "words", "words" ] ]); + assert.throws(() => { + df.pctChange(1); + }, Error, "TypeError: pctChange operation is not supported for string dtypes"); + }); + }); + describe("diff", function () { it("Return same DataFrame if other === 0", function () { const data = [ [ 0, 2, 4 ], [ 10, 10, 10 ], [ 1, 2, 3 ] ]; diff --git a/src/danfojs-node/test/core/frame.test.ts b/src/danfojs-node/test/core/frame.test.ts index 17e195ea..42ccb7e5 100644 --- a/src/danfojs-node/test/core/frame.test.ts +++ b/src/danfojs-node/test/core/frame.test.ts @@ -1037,6 +1037,51 @@ describe("DataFrame", function () { }); }); + describe("pctChange", function () { + it("Return same DataFrame if other === 0", function () { + const data = [[0, 2, 4], [10, 10, 10], [1, 2, 3]]; + const df = new DataFrame(data); + assert.deepEqual((df.pctChange(0) as DataFrame).values, [[0, 2, 4], [10, 10, 10], [1, 2, 3]]); + }); + it("Return difference in percentage of DataFrame with previous row", function () { + const data = [[90], [900], [900]]; + const df = new DataFrame(data); + assert.deepEqual((df.pctChange(1) as DataFrame).values, [[NaN], [9], [0]]); + }); + it("Return difference in percentage of DataFrame with following row", function () { + const data = [[0, 5, 15], [10, 10, 10], [1, 2, 5]]; + const df = new DataFrame(data); + assert.deepEqual((df.pctChange(-1) as DataFrame).values, [[-1, -0.5, 0.5], [9, 4, 1], [NaN, NaN, NaN]]); + }); + it("Return difference in percentage of a DataFrame with a Series along default axis 1", function () { + const data = [[0, 2, 4], [10, 10, 10], [1, 2, 3]]; + const sf = new Series([1, 2, 1]); + const df = new DataFrame(data); + assert.deepEqual((df.pctChange(sf) as DataFrame).values, [[-1, 0, 3], [9, 4, 9], [0, 0, 2]]); + }); + it("Return difference in percentage of a DataFrame with along axis 0 (column-wise), previous column", function () { + const data = [[0, 2, 4], [10, 10, 10], [1, 2, 3]]; + const df = new DataFrame(data); + assert.deepEqual((df.pctChange(1, { axis: 0 }) as DataFrame).values, [[NaN, -1, 1], [NaN, 0, 0], [NaN, 1, 0.5]]); + }); + it("Return difference in percentage of a DataFrame with along axis 0 (column-wise), following column", function () { + const data = [[0, 2, 4], [10, 10, 10], [1, 2, 8]]; + const df = new DataFrame(data); + assert.deepEqual((df.pctChange(-1, { axis: 0 }) as DataFrame).values, [[-1, -0.5, NaN], [0, 0, NaN], [-0.5, -0.75, NaN]]); + }); + it("Return difference in percentage of a DataFrame with another DataFrame along default axis 1", function () { + const df1 = new DataFrame([[0, 2, 4], [3, 10, 4]]); + const df2 = new DataFrame([[-1, -2, 4], [6, 5, 0]]); + assert.deepEqual((df1.pctChange(df2) as DataFrame).values, [[-1, -2, 0], [-0.5, 1, -1]]); + }); + it("Throw error if DataFrame for pctChange contains string", function () { + const df = new DataFrame([["words", "words", "words"], ["words", "words", "words"]]); + assert.throws(() => { + df.pctChange(1); + }, Error, "TypeError: pctChange operation is not supported for string dtypes"); + }); + }); + describe("mod", function () { it("Return modulus of DataFrame with a single Number", function () { const data = [[0, 2, 4], [360, 180, 360]];