# Method Chaining Sometimes, data science projects involve manipulating an object through a series of events. For example, a matrix may need to be transposed first, added some shape sentitive non-linear transformations, and transposed back. Suppose the matrix object is assigned `m`, and the method to transpose it is `t()` and that to transform it `s()`, then the operations just stated are essentially something like this in the pseudo-code: ```{} m = new object of class Matrix m = m.t() m = m.s() m = m.t() ``` But suppose there are way more steps than 3, as shown in the example above. Maybe you are considering writing something like the following to work the same, which is what this article is about to share with you. ```{} m = new object of class Matrix m.t().s().t() ``` The trick is for every method you want chainable to always return the object, i.e. `this` or `self`, etc. depending on the language you are using. The following are just illustrative examples. # An Illustrative Example in Java Say you needed a Matrix class that can transpose, append, calculates sums, etc. You can do it this way. Notice how the methods always return `this`. ```{} public class Mat { int dim1; int dim2; int[][] m; // matrix data; Mat(int[][] m) { this.m = m; updateDims(); } void updateDims() { this.dim1 = m.length; this.dim2 = m[0].length; } static int vSum(int[] v) { int sum = 0; for(int elem: v) sum += elem; return sum; } // s is for sums Mat s() { return this.s(0).s(1); } // s is for summing over a dimension Mat s(int dim) { if(dim == 0) { int[] rowSums = new int[dim1]; for (int i = 0; i <= dim1 - 1 ; i++) { rowSums[i] = vSum(m[i]); } return this.a(rowSums, 1); } else if (dim == 1) { return this.t().s(0).t(); } else { return e("Dim must be either 0 (row) or 1 (column)! Returning the original matrix."); } } Mat a(int[] v) { return this.a(v, 0, -1); } Mat a(int[] v, int dim) { return this.a(v, dim, -1); } Mat a(int[] v, int dim, int loc) { // append at loc location if(dim == 0) { if(loc < 0) loc += dim1 + 1; // e.g. if you type -1, you get dim1, which is the bottom most location if(loc < 0 || loc > dim1) return e("Specified location out of bound."); if(v.length != dim2) return e("Cannot append the vector to rows. Vector length must be the same as the number of columns."); int[][] output = new int[dim1 + 1][dim2]; boolean newVectorAppended = false; for(int i = 0; i <= dim1 - 1; i++) { if(i == loc) { output[i] = v; newVectorAppended = true; } if(!newVectorAppended) { output[i] = m[i]; } else { output[i + 1] = m[i]; } } if(!newVectorAppended) output[dim1] = v; m = output; updateDims(); return this; } else if (dim == 1) { if(loc < 0) loc += dim2 + 1; // e.g. if you type -1, you get dim2 - 1, which is the rightmost location if(loc < 0 || loc > dim2) return e("Specified location out of bound."); if(v.length != dim1) return e("Cannot append the vector to columns. Vector length must be the same as the number of rows."); return this.t().a(v, 0, loc).t(); } else { return e("Dim must be either 0 (row) or 1 (column)! Returning the original matrix."); } } // t is for transpose Mat t() { int[][] output = new int[dim2][dim1]; for(int i = 0; i <= dim1 - 1; i++) { for(int j = 0; j <= dim2 - 1; j++) { output[j][i] = m[i][j]; } } m = output; updateDims(); return this; } // r is for reversing the matrix Mat r(int dim){ // if dim == 0; by Row; if dim == 1; by Column int[][] output = new int[dim1][dim2]; // reverse by columns if (dim == 1) { for(int i = 0; i <= dim1 - 1; i++) { for(int j = dim2 - 1; j >=0; j--) { output[i][dim2 - j - 1] = m[i][j]; } } // reverse by columns } else if (dim == 0) { for(int i = dim1 - 1; i >= 0; i--) { for(int j = 0; j <= dim2 - 1; j++ ) { output[dim1 - i - 1][j] = m[i][j]; } } } else { return e("the dim argument must either be 1 (byCol) or 0 (byRow)."); } m = output; updateDims(); return this; } // p is for print Mat p() { for(int[] v: m) { for(int elem: v) { System.out.print(elem + "\t"); } System.out.println(); } return this; } // e is for error Mat e() { return e("Something went wrong."); } Mat e(String msg) { System.out.println(msg +"\n Returning the original matrix."); updateDims(); return this; } // d is for divider Mat d() { return d(10); } Mat d(int num) { for(int i = 0; i <= num - 1 ;i++) System.out.print("-"); System.out.print("\n"); return this; } } ``` The set up makes it easy to use the class `Mat`, this way: `m.p().d().t().r(1).p();` // in Main.java ```{} public class Main { public static void main(String[] args) { //input // 1 2 3 4 5 6 // 7 8 9 10 11 12 // 13 14 15 16 17 18 // 19 20 21 22 23 24 // 25 26 27 28 29 30 // expected output // ------ // 25 19 13 7 1 // 26 20 14 8 2 // 27 21 15 9 3 // 28 22 16 10 14 // 29 23 17 11 5 // 30 24 18 12 6 int[][] ias = { { 1, 2, 3, 4, 5, 6 }, { 7, 8, 9, 10, 11, 12 }, { 13, 14, 15, 16, 17, 18 }, { 19, 20, 21, 22, 23, 24 }, { 25, 26, 27, 28, 29, 30 } }; Mat m = new Mat(ias); m.p().d().t().r(1).p(); } } ``` # Broader Use Imagine a scenario with Python where you are streamlining your data analytic process a certain way you want reproducible. Your class may (conceptually) look like this: ```{python} import numpy as np import pandas as pd class YourModel: raw_data = None cleaned_data = None regression_output = None def __init__(self, file_path): self.load_data(). .. # run as many methods as necessary def load_data(self, file_path): # some check ups here assert isinstance(file_path, str) self.raw_data = pd.read_excel(file_path) #... return self def prepare(self, some_criteria): assert self.raw_data is not None cleaned_df = pd.DataFrame() #... some cleaning here based on criteria self.cleaned_data = cleaned_df return self def fit_regression(self): assert self.cleaned_data is not None # something from scikitlearn maybe self.regression_output = Regression().fit(self.cleaned_data) return self ... ``` And for people who use your API, they simply write this: ```{} mod = YourModel(necessary_arguments) mod.prepare(args).fit_regression() ... ``` The structure can make hyperparameter tuning a lot friendlier than it usually is. Hope you enjoyed reading this article. Happy New Year = ] ###### tags: `data science` `Python` `Java`