Linear Regression Class in Python I have recently been brushing up on my statistics and calculus and wanted to implement Linear Regression, the code will be for a calculus/statistics library I am working on (I know there are libraries for this but I am trying improve my both coding and math skills).

The following code works as intended. However, I feel like it has some structural flaws and I can't put my finger on what it is

class LinearReg:

    x = []
    y = []

    x_mean = 0
    y_mean = 0 

    b_zero = 0
    slope = 0

    def __init__(self,x,y):

        if len(x) != len(y):
            raise Error("Both axis must have the same number of values")

        self.x = x
        self.y = y
        self.x_mean = self.axis_mean(self.x)
        self.y_mean = self.axis_mean(self.y)

    def axis_mean(self,axis):
        return sum(axis) / len(axis)


    def sum_of_deviation_products(self):
        result = 0
        for i in range(len(self.y)):
            x_dev = (self.x[i] - self.x_mean)
            y_dev = (self.y[i] - self.y_mean)
            result += x_dev * y_dev
        return result

    def sum_of_x_deviation_squared(self):
        result = 0
        for i in range(len(self.x)):
            result += (self.x[i] - self.x_mean)**2
        return result

    def get_b_zero(self):
        return self.b_zero


    def get_slope(self):
        self.slope = self.sum_of_deviation_products() / self.sum_of_x_deviation_squared()
        return self.slope

    def fit_best_line(self):
        self.b_zero = self.y_mean - (self.get_slope() * self.x_mean)
        print("The best line equation is: " + 
        "%.2f" % self.slope
         + "x + " + 
         "%.2f" % self.b_zero)


My primary focus is now: structure and cleanliness, 


How efficient would this play out in a large library?
How clean and readable is the code?
What kind of problems would I face if this class were to interact with other classes in the library?


Perhaps there is quicker way to do Linear Regression in Theory, but I'm not really interested in that right now.
Toward optimized functionality and design


since all crucial attributes x, y, x_mean, y_mean are initialized on LinearReg instance creation (within __init__ method) - no need to duplicate and define them as class attributes (x = [] ... y = []), those should be removed as redundant
raise Error - Error is not Python exception class. Use Exception or ValueError
axis_mean(self, axis) function does not use self context and deserves to be just a @staticmethod
sum_of_deviation_products/sum_of_x_deviation_squared functions 
Substitute algorithm: instead of going with external variable result + range(len(..)) + for loop - apply a convenient combination of sum + enumerate functions:

def sum_of_deviation_products(self):
    return sum((self.x[i] - self.x_mean) * (y - self.y_mean)
            for i, y in enumerate(self.y))


A good (or better) alternative would be sum + zip approach: sum((x - self.x_mean) * (y - self.y_mean) for x, y in zip(self.x, self.y))
get_b_zero method is redundant as it's just return a public attribute self.b_zero (which is accessed directly)
get_slope method 
Instead of storing and reassigning self.slope attribute on each method invocation - as it's recalculated each time, it deserves to be a computed attribute using @property decorator:

@property
def slope(self):
    return self.sum_of_deviation_products() / self.sum_of_x_deviation_squared()


Now, it's simply accessed with self.slope. 
fit_best_line method 
Instead of unreadable string concatenation like ... + "%.2f" % self.slope + "x + " + "%.2f" % self.b_zero use flexible f-string formatting:

print(f"The best line equation is: {self.slope:.2f}x + {self.b_zero:.2f}").

The conciseness and flexibility are obvious.
Also, in case if b_zero attribute/property happen to be only actual in context of fit_best_line call - it can be eliminated and declared as just local variable.




The final optimized approach:

class LinearReg:
    def __init__(self, x, y):
        if len(x) != len(y):
            raise ValueError("Both axis must have the same number of values")

        self.x = x
        self.y = y
        self.x_mean = self.axis_mean(self.x)
        self.y_mean = self.axis_mean(self.y)
        self.b_zero = 0

    @staticmethod
    def axis_mean(axis):
        return sum(axis) / len(axis)

    def sum_of_deviation_products(self):
        return sum((self.x[i] - self.x_mean) * (y - self.y_mean)
                   for i, y in enumerate(self.y))

    def sum_of_x_deviation_squared(self):
        return sum((x - self.x_mean) ** 2
                   for x in self.x)

    @property
    def slope(self):
        return self.sum_of_deviation_products() / self.sum_of_x_deviation_squared()

    def fit_best_line(self):
        self.b_zero = self.y_mean - (self.slope * self.x_mean)
        print(f"The best line equation is: {self.slope:.2f}x + {self.b_zero:.2f}")