1
\$\begingroup\$

I know this is a weird program but I am trying to learn how to write Python programs. This program will accept user input, starting by having the user specify a file path then continuously looping through a set of option which the user will choose.

The options are basic Pandas methods, get mean, variance, and standard deviation of a certain row, group certain columns, get info on csv file path. My biggest question is how could I better structure this file, how can it be more pythonic?

Suggestions on how I can use method overloads better are welcome. For example, my GroupCols method, is that the best way to allow for method overloading? Should I have if __name__ "__main__": at the very bottom of the program? Would it be better to have all of my methods as return functions and then print() the results after calling each function? Or the function just print()?

import pandas as pd

class Data:
    def __init__(self):
        self.df = None

    def OpenFile(self, filePath: str):
        try:
            self.df = pd.read_csv(filePath)
        except Exception as ex:
            print(str(ex))
            print("Reenter file path before proceeding...\n")
            return

    def Head(self, coun: int = 0):
        if coun == 0:
            print(self.df.head())
            return
        print(self.df.head(coun))

    def Info(self):
        print(self.df.info())

    def GroupCols(self, col1: str, col2: str = "", col3: str = ""):
        try:
            if not col2 and not col3:
                grouped = self.df.groupby([col1])
                self.GetStats(grouped)
            elif col2 and not col3:
                grouped = self.df.groupby([col1, col2])
                self.GetStats(grouped)
            elif col2 and col3:
                grouped = self.df.groupby([col1, col2, col3])
                self.GetStats(grouped)
            elif not col2 and col3:
                grouped = self.df.groupby([col1, col3])
                self.GetStats(grouped)
        except Exception as ex:
            print(str(ex))
        return

    def GetCount(self, col: str) -> int:
        if col:
            return self.df[col].value_counts()
        return 0

    def GetStats(self, dfStats):
        print("Mean : %r" %(dfStats.mean()))
        print("Variance: %r" % (dfStats.var()))
        print("Std Dev: %r" % (dfStats.std()))

def main():
    d = Data()
    while True:
        option = input("Choose an option:\n1 - Open New File\n2 - Get Head of file\n"+
                       "3 - Get Info on file\n4 - Group between 1 and 3 column\n"+
                       "5 - Get count of specified column\n6 - Exit\n")
        if option == "1":
            file = input('Enter the file path of the csv file:\n')
            d.OpenFile(file)
            continue

        elif option == "2":
            count = input("Enter number of rows to retrieve or enter 0\n")
            d.Head(int(count))
            continue

        elif option == "3":
            d.Info()

        elif option == "4":
            cols = input("Enter between 1 and 3 columns separated by a comma\n")
            group = cols.split(',')
            if len(group) == 1:
                d.GroupCols(group[0])
            elif len(group) < 3:
                d.GroupCols(group[0], group[1])
            else:
                d.GroupCols(group[0], group[1], group[2])

        elif option == "5":
                col = input("Enter column name to get count of\n")
                print(d.GetCount(col))

        elif option == "6":
            exit()

        else:
            print("Could not understand option. Enter numeric value\n")
            



if __name__ == "__main__":
    main()
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Starting from your questions:

  • ...GroupCols method, is that the best way to allow for method overloading? I guess if what you mean is whether the way the function is defined does a function overload depending on the number of parameters you provide, then it technically does it. But it is not really the best way to deal with what you aim for there. Also, as you check for not col2 and not col3 I think the type and default in the function definition should be more like col2: Optional[str] = None, if you want to make sure that checking on the parameters being provided works as expected.
  • Should I have if __name__ "__main__": at the very bottom of the program? Yes. This is ok as you have it.
  • Would it be better to have all of my methods as return functions and then print() the results after calling each function? Or the function just print()? My recommendation would be that, unless the function name conceptually states that it will print (such as the display function), it would be better for it to return a value to be printed. Also, if you opt for returning nothing, it should be typed in the function description (def Info(self) -> None:)

As other quick notes:

  • You are consistently checking in several functions (GroupCols and GetCount) str="" as if this would be False or None, and that is not the case. if str="" -> bool(str)==True. So be mindful of that, empty string is a value of string.
  • Considering the type of functions you created, I am not 100% sure there is any benefit from you making them as methods of a class. It would make much more sense for them to just be functions to which a dataframe is provided.
  • To be honest, you barely need any function, as all your calls can be directly processed by DataFrame methods, but as you say that this is a learning exercise I will not go into that.
  • Naming convention in python would dictate that the functions would be named as lowercase, underscored, not camelcase. Then GroupCols -> group_cols and so on.
  • for GroupCols and GetCount it would be good to check if the provided columns exist in the dataframe. On the first one you have controlled the error that it will produce if not, but that is not the case on the second function.

On code simplification:

pandas head method already takes dataframe size 0 into account:

 def Head(self, coun: int = 0) -> None:
     print(self.df.head(coun))

The GroupCols function should probably deal with an iterable as input. It would make it much more flexible and easier to read/understand (you'll need to from typing import Iterable).

 def GroupCols(self, cols: Iterable[str]) -> None:
    try:
        grouped = self.df.groupby(cols)
        self.GetStats(grouped)
    except Exception as ex:
        print(str(ex))
    return

You'll just need to make sure to call it in you main loop as d.GroupCols(group) and it will work for any number of columns.

Hope this helps!

\$\endgroup\$
1
  • \$\begingroup\$ thanks I will use this advice. I think the method overloading I was wondering the best way to use it and did not think None being newer to python \$\endgroup\$
    – xtryingx
    Aug 10, 2021 at 4:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.