Univariate linear regression from scratch in Python
About OOP


  I tried to exploit OOP as much as I could, instead of using a procedural approach to write the algorithm.


Although I believe that your approach was fine, using OOP for the sake of OOP is something I would rather warn against. There is a talk about this here.

Comments

def __init__(self, dataset, learning_rate, num_iterations):
    """
    Class constructor.
    """


The comment Class constructor is redundant and unecessary, I would instead explain the parameters of __init__ in the doc string.

    self.M = len(self.dataset) # 100.


Is the # 100 saying that the len(self.dataset) is always going to be 100? It might be 100 in this case, but I highly doubt you can ensure that.

Default values

Have you considered putting default values for learning_rate and num_iterations? If we want a default of 100 and 0.001 for num_iterations and learning_rate respectively, you could rewrite __init__ like:

def __init__(self, dataset, learning_rate=0.001, num_iterations=100):


Private methods

Do you really want do_gradient_step(self) to be considered public? Yes, there are no "true" private methods, but the convention is to put one underscore before the name to indicate it is private. Honestly, I would just call it _step(self).

Indentation

if __name__ == "__main__": main()


should really be:

if __name__ == "__main__": 
    main()


To comply with PEP 8.