# 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`