Presenting and marketing your portfolio
- Last updated on July 7, 2023 at 11:56 PM
Now that you know how to create and upload a repository, we’ll cover how to present it. Before we go through this section, it may be useful to look at some example projects:
Requirements
It’s important to make sure anyone can install and run your work. Even if your work is a Jupyter Notebook, there may be packages other people need to install. You can list out all the packages your project is using with pip freeze
if you’re using a virtual environment. You’ll get output like this:
$ pip freeze
Django==1.10.5
MechanicalSoup==0.6.0
Pillow==4.0.0
The output is the library name, then a version number. The above output tells us that we have
Django
version 1.10.5
installed, for example. You’ll want to copy these requirements into a folder in your project called requirements.txt
. The file should look like this:
Django==1.10.5
MechanicalSoup==0.6.0
Pillow==4.0.0
Now, anyone can install the requirements for your project using pip install -r requirements.txt
. This will install the exact version of the libraries that we have on our machine. If you want to instead install whatever the latest version of each library is, you can leave off the version numbers in requirements.txt
:
Django
MechanicalSoup
Pillow
If you want to make a requirements.txt
file, but didn’t use a virtual environment for your project, you’ll want to manually look through the libraries you imported in your project, and add them to requirements.txt
without version numbers, like:
pandas
numpy
Pillow
Paths
It’s common when you’re working locally to hardcode absolute paths to data files, like /Users/vik/Documents/data.csv
. Other people who want to run your project won’t have those same paths on their computers, so they won’t be able to run your project. You can fairly easily replace these with relative paths, which allow people who have the data in the same folder as your project but don’t have the same absolute paths, to use your code. Let’s say we have this code:
with open("/Users/vik/Documents/data.csv") as f:
data = f.read()
Let’s say our project code is at /Users/vik/Documents/Project.ipynb
. We can replace the code with a relative path, like:
with open("data.csv") as f:
data = f.read()
It’s generally a good idea to put the data in the same folder as your project, or in a subfolder, to make relative paths and loading the data easier.
Additional files
By default, running git add .
and git commit -m "Message""
will add all the files in a folder to a git commit. However, there are many artifact files that you don’t want to be added. Here’s an example folder:
loans
│ __pycache__
│ main.py
│ main.pyc
│ temp.json
│
└───data
│ test.csv
│ train.csv
Note files like __pycache__
, main.pyc
, and temp.json
. The main code of the project is in main.py
, and the data is in data/test.csv
, and data/train.csv
. For someone to run the project, those are the only files they need. Folders like __pycache__
and main.pyc
are automatically generated by Python when we run code or install packages. These enable Python scripts and package installation to be faster and more reliable. However, these files aren’t part of your project, and thus shouldn’t be distributed to others. We can ignore files like this with a .gitignore
file. We can add a .gitignore
file to our folder:
loans
│ .gitignore
│ __pycache__
│ main.py
│ main.pyc
│ temp.json
│
└───data
│ test.csv
│ train.csv
The content of the .gitignore
file is a list of files to ignore. We can create a .gitignore
file, then add *.pyc
and __pycache__
to ignore the generated files in our folder:
*.pyc
__pycache__
This still leaves the temp.json
file. We can add another line to ignore this file:
*.pyc
__pycache__
temp.json
This will ensure that these files are not tracked by git, and added to new git commits when you run git add .
. However, if you’ve already added the files to a git commit before, you’ll need to remove them first with git rm temp.json --cached
. It’s recommended to create a .gitignore
file as soon as possible, and to add entries for temporary files quickly. You can find a good starter "gitignore" file here. It’s usually recommended to use this as your .gitignore
file, then add new entries as needed. It’s a good idea to ignore any dynamic, generated, or temporary files. You should only commit your source code, documentation, and data (depending on how large your data is — we’ll cover this in another section).
Secret keys or files
Many projects use secret keys to access resources. A good example is api keys, such as AWS_ACCESS_KEY="3434ffdsfd"
. You absolutely don’t want to share your secret keys with other people — this allows them to access your resources, and could cost you money. Here’s some example code that uses a secret key:
import forecastio
forecast = forecastio.load_forecast("34343434fdfddf", 37.77493, -122.41942)
In the above code,"34343434fdfddf"
is a secret key that we’re passing into a library to get a weather forecast. If we commit the code as is, anyone browsing Github will be able to see our secret data. Fortunately, there’s an easy way to fix this, and enable anyone using the project to supply their own keys, so they can still run the code. First, we create a file called settings.py
, with the following lines:
API_KEY = ""
try:
from .private import *
except Exception:
pass
The above code defines a key calledAPI_KEY
. It also tries to import from a file called private.py
, and doesn’t do anything if the file doesn’t exist. We then need to add a private.py
with the following content:
API_KEY = "34343434fdfddf"
Then, we need to add private.py
to .gitignore
so it doesn’t get committed:
private.py
Then, we modify our original code:
import settings
forecast = forecastio.load_forecast(settings.API_KEY, 37.77493, -122.41942)
All the changes we’ve made above result in the following:
- The code imports the settings file
- The settings file imports the
private.py
file- This overwrites the
API_KEY
variable in the settings file with theAPI_KEY
defined in the private file
- This overwrites the
- The code uses
API_KEY
from the settings file, which equals"34343434fdfddf"
The next time you make a git commit, private.py
will be ignored. However, if someone else looks at your repository, they’ll see that they need to fill out settings.py
with their own settings to get things to work properly. So everything will work for you, you won’t share your secret keys with others, and things will work for others.
Large or restricted data files
It’s important to look at the user agreement when you’re downloading a data file. Some files are not allowed to be redistributed. Some files are also too large to make downloading useful. Other files are updated quickly, and distributing them doesn’t make sense — you want the user to download a fresh copy. In cases like these, it makes sense to add the data files to the .gitignore
file. This ensures that the data file won’t be included in the repository. It’s important to have information on how to download the data in the README.md
, though. We’ll cover this in the next section.
The README file
The README file is very critical to your project. The README is usually named README.md
, and is in Markdown format. GitHub will automatically parse Markdown format and render it. Your README file should describe:
- The goals of your project
- Your thought process and methods in creating the project
- How to install your project
- How to run your project
You want an average technically competent stranger to be able to read your README file and then run the project on their own. This ensures that more technical hiring managers can reproduce your work and check your code. You can find good README examples here and here. It’s important to go through the installation steps yourself in a new folder or on a new computer, to make sure everything works. The README is also the first and potentially only thing someone will look at, because GitHub renders it below the repository file view. It’s important to “sell” what the project is, why you made it, and what’s interesting about it. Here’s an example:
# Loan Price Prediction
In this project, I analyzed data on loans issued through the [LendingClub](https://www.lendingclub.com/) platform. On the LendingClub platform, potential lenders see some information about potential borrowers, along with an interest rate they'll be paid. The potential lenders then decide if the interest on the loan is worth the risk of a default (the loan not being repaid), and decide whether to lend to the borrower. LendingClub publishes anonymous data about its loans and their repayment rates.
Using the data, I analyzed factors that correlated with loans being repaid on time, and did some exploratory visualization and analysis. I then created a model that predicts the chance that a loan will be repaid given the data surfaced on the LendingClub site. This model could be useful for potential lenders trying to decide if they should fund a loan. You can see the exploratory data analysis in the `Exploration.ipynb` notebook above. You can see the model code and explanations in the `algo` folder.
Your README should be more extensive and go into more depth than the above example, but this is a good starting point. Ideally, you’d also want:
- Some bullet points with interesting observations you found in the exploration
- Any interesting charts or diagrams you created
- Information about the model, such as algorithm
- Error rates and other information about the predictions
- Any notes about real-world usage of the model
The summary here is that the README is the best way to sell your project, and you shouldn’t neglected it. Don’t spend a lot of effort making a good project, then have people skip looking through it because they don’t find it interesting.
Inline explanations
If you’re writing Python script files, you’ll want to include lots of inline comments to make your logic easier to follow. You don’t want to share something like this:
def count_performance_rows():
counts = {}
with open(os.path.join(settings.PROCESSED_DIR, "Performance.txt"), 'r') as f:
for i, line in enumerate(f):
if i == 0:
continue
loan_id, date = line.split("|")
loan_id = int(loan_id)
if loan_id not in counts:
counts[loan_id] = {
"foreclosure_status": False,
"performance_count": 0
}
counts[loan_id]["performance_count"] += 1
if len(date.strip()) > 0:
counts[loan_id]["foreclosure_status"] = True
return counts
A better alternative is:
def count_performance_rows():
"""
A function to count the number of rows that deal with performance for each loan.
Each row in the source text file is a loan_id and date.
If there's a date, it means the loan was foreclosed on.
We'll return a dictionary that indicates if each loan was foreclosed on, along with the number of performance events per loan.
"""
counts = {}
# Read the data file.
with open(os.path.join(settings.PROCESSED_DIR, "Performance.txt"), 'r') as f:
for i, line in enumerate(f):
if i == 0:
# Skip the header row
continue
# Each row is a loan id and a date, separated by a |
loan_id, date = line.split("|")
# Convert to integer
loan_id = int(loan_id)
# Add the loan to the counts dictionary, so we can count the number of performance events.
if loan_id not in counts:
counts[loan_id] = {
"foreclosure_status": False,
"performance_count": 0
}
# Increment the counter.
counts[loan_id]["performance_count"] += 1
# If there's a date, it indicates that the loan was foreclosed on
if len(date.strip()) > 0:
counts[loan_id]["foreclosure_status"] = True
return counts
In the above, it’s much more clear what the function is doing, and why. It’s important to reduce the mental effort of following your logic as much as possible. Reading code is time-consuming, and not everyone looking through your project will make that investment. Comments make things smoother, and ensure that more people read through your project.
Jupyter Notebooks
Jupyter Notebooks, like this one, are automatically rendered by GitHub, so people can view them in the browser. It’s important to verify a few things for every notebook you upload:
- Make sure it looks good when it renders in the interface
- Make sure explanations are frequent, and it’s clear what’s happening at each step
- A good ratio is no more than 2 code cells per markdown cell
- Ensure all explanations in the notebook are clear and easy to follow.
- Make sure the README links to the notebook and briefly explains what you did in the notebook
The second and last step is especially important. You want people to be able to easily figure out that your analysis is in the notebook, and what analysis you did. A notebook with only code cells is very hard to follow and doesn’t demonstrate your data skills. Employers are looking for people who can code and communicate effectively.
Making your work public
After you’ve followed all the steps above, you’ll want to do a final review of your project, then set it public. You can do this from the repository settings button.
Next Steps
You now know how to put projects on GitHub, and hopefully have a few projects you can upload. The next step is to add your projects to your resume and portfolio pages. Some things to consider:
- If you have a blog or personal website, write about each project as a separate post that goes into depth about how you built it, and what you found
- Add your projects to your LinkedIn profile
- You can publish them on sites like Medium, Reddit or the Dataquest community
- List your projects on your resume
If you have dozens of projects and don’t want to add links to each repository in your resume, one strategy is to make a single Github repository called Portfolio. In the README of this repository, list all of your projects, along with a short explanation of each one and a link. This will enable you to share a single link, but still let people see all of your projects. Make sure to provide good explanations, so people are willing browse.
You should now know enough to put your portfolio on Github and impress hiring managers.
More in this series: