
Learn Python with Basketball (part 2)
This is part 2 of the Learn Python with NBA data beginner series. In this post, we cover the namespace, built-in objects, what libraries are, and user-defined functions. We learn about all of this cool stuff and then use it make our code from last time slightly more Pythonic.
Recap
In this part of the beginner series, we will build upon the code from the last post, introducing more layers and moving parts. We will discuss the Python namespace and how Python keeps track of all the objects we have defined in our program. Additionally, we will learn a little bit about libraries and how we can use them to import code into our namespace (although we won't be importing anything just yet) and built-in objects (which we've actually already used). We will also cover user-defined functions and functions in general.
Built-in objects and the namespace
Built-in functions are functions (we touched briefly on this last time) that exist naturally in the Python namespace. The namespace sounds very arcane, but essentially it is a collection of already defined names for things that we can use in our code.
Namespaces
I asked chatGPT to explain the namespace, to which it replied:
Python namespace is a collection of objects (variable, function, class, etc.) that can be accessed by a unique name. It's basically a way for Python to keep track of all the different objects that you have defined in your program, and make sure that each object has a unique name. This helps to prevent confusion and name collisions between different objects in your program.
We can import things in to our namespace, as we'll see in later posts, to bring in more items (functions and classes) to write our code. Importing external code will be useful when we start using libraries like pandas, Python's data manipulation library. A library is a bunch of code already written for you so you don't have to reinvent the wheel. When you import those libraries into your code you are then able to use them once you get them into the namespace.
But there are certain objects that already exist without any necessary importing. These are called "built-in" objects.
One such built-in function here is the print function, which we used in the last post.
print('Hello World!')
Hello World!
We touched only briefly on functions last time, but essentially in Python, functions are blocks of code that are defined once and can be used repeatedly throughout a program. Functions allow for efficient and organized code by breaking down tasks into smaller, reusable chunks. They can accept arguments (inputs) and return values (outputs). Functions can also be used to create modules, which are collections of related functions that can be imported into other Python scripts (as discussed, we'll be exploring some of these modules/libraries in later posts like pandas). Functions are a key part of Python programming and are used to make code more modular, maintainable, and readable.
User-defined Functions
User defined functions in Python are functions that are created and defined by the user, rather than being pre-defined in the language itself. Using a fantasy baskeball example, a user may define a function that calculates the total number of points scored by a player in a given week. This function would take in parameters such as the player's name, their position, and the number of three point field goals, assists, etc. they had, and would return the total number of points scored. This user defined function could then be called and used multiple times throughout the program to calculate points for different players.
To write a user-defined function in Python using an NBA example, you could follow these steps:
1. Define the function by using the keyword def followed by the function name and any parameters that will be passed to the function. For example, a function that calculates a player's average shooting percentage might be defined like this:
def shooting_percentage(field_goals, field_goals_attempted):
2. Inside the function, write the code that will perform the desired calculation. In the example above, this would be the code that divides the player's field goals by their field goal attmepts:
percentage = field_goals / field_goals_attmepted
3. Return the result of the calculation by using the return keyword. In the example above, this would be the player's shooting percentage:
return percentage
4. Call the function in your code by using the function name followed by any necessary arguments. For example, to calculate the shooting percentage for a player who had 10 field goals on 20 field goal attempts, you would write:
shooting_percentage = shooting_percentage(10, 20)
5. Print the result of the function call to see the result of the calculation. In the example above, this would be the player's shooting percentage:
print(shooting_percentage) # Output: 0.5
Overall, a user-defined function in Python using an NBA example might look like this:
def shooting_percentage(field_goals, field_goals_attempted):
percentage = field_goals /field_goal_attempts
return percentage
shooting_percentage = shooting_percentage(10, 20)
print(shooting_percentage)
0.5
Now let's take all of this new information and use it to make our example from the last post even better. How can we modularize our code in a way that makes it better? The answer is that we can write a function that takes in a player's name, number of field goals and field goal attempts and outputs a human-readable string that tells us that player's shooting percentage.
def calculate_shooting_percentage(player_name, field_goals, field_goals_attempted):
shooting_percentage = field_goals / field_goals_attempted
shooting_percentage_rounded = round(shooting_percentage, 2)
print(player_name, 'had a shooting percentage of', shooting_percentage_rounded)
calculate_shooting_percentage('Stephen Curry', field_goals=8.4, field_goals_attempted=19.1)
Stephen Curry had a shooting percentage of 0.44
This is our simple Python function that calculates the shooting percentage of a player. The function takes in three arguments:
The function first calculates the shooting percantage by dividing the number of field goals by the number of field goal attempts. It then rounds the shooting percentage to two decimal places and prints out a string that includes the player's name and their shooting percentage.
In the example provided, the function is called with the player name "Stephen Curry", 8.4 field goals, and 19.1 field goal attempts. This means that Stephen Curry had a shooting percentage of 0.43979057591623033, which gets rounded to 0.44 and gets printed out as part of the string.
Positional and Keyword Arguments
Here you can also see that we explicitly defined arguments when passing them in. "Stephen Curry" was a positional argument and field goals and field goal attempts were keyword arguments.
In programming, a keyword argument is a type of argument that is passed to a function or method in which the name of the argument is specified in the function call. This allows for greater clarity and readability of the code, as the name of the argument clearly indicates its purpose.
As an example, consider a function that calculates the average rushing yards per game for a given NBA player. This function might have a keyword argument called "player_name" that specifies the name of the player for whom the average three point shooting percentage per game should be calculated. The function could then be called like this:
calculate_three_point_shooting_percentage_per_game(player_name="Jayson Tatum")
In this example, the "player_name" keyword argument is used to specify the player for whom the three point shooting percentage per game should be calculated. The use of a keyword argument makes the code more readable and easier to understand, as it is clear that the "player_name" argument is used to specify the player in question. Our example above is no different.
Let's circle back to introducing this new idea of user-defined functions in to our code.
players = [
{
"name": "Stephen Curry",
"field_goals": 8.4,
"field_goals_attempted": 19.1
},
{
"name": "Giannis Antetokounmpo",
"field_goals": 10.3,
"field_goals_attempted": 18.6
},
{
"name": "Jayson Tatum",
"field_goals": 9.3,
"field_goals_attempted": 20.6
}
]
for player in players:
calculate_shooting_percentage(player['name'], field_goals=player['field_goals'], field_goals_attempted=player['field_goals_attempted'])
Stephen Curry had a shooting percentage of 0.44
Giannis Antetokounmpo had a shooting percentage of 0.55
Jayson Tatum had a shooting percentage of 0.45
As you can see here, we bring in our list of dictionary objects, each containing information about 3 NBA players - Stephen Curry, Giannis Antetokounmpo, and Jayson Tatum.
We then iterate through each of our player objects and pass in their data to the function, which provides the desired output! Compare this to our previous post and you could probably notice that the code is a lot cleaner now.
Concluding Thoughts
We covered a decent amount of theory in this post. To recap, we:
We also covered in-depth what functions are and some other details like positional and keyword arguments.
Using all of this, we took our code from last time and made it slightly more Pythonic (emphasis on slightly because at the moment, our code isn't all that more Pythonic yet, but we'll touch on this topic more in future posts. "Pythonic" code is essentially Python code that is written with good style/convention).
No groundbreaking analysis yet, but we're one step closer to do some real analysis in Python!
Thanks for reading - You guys are all awesome and happy coding!