The Fastest and Easiest Way to Forecast Data on Python | by Rohan Gupta | Towards Data Science

Recently, I learned about Prophet (fbprophet). If you’re a Data Scientist who works with time-series data, you will love this tool.
For those not aware, Prophet was developed by Facebook to aid Data Scientists with automated forecasting for time-series data through its simple Sk-Learn style API. Prophet can be fine-tuned by a data scientist to achieve more specificity. It is an additive forecasting model, and assumed that seasonal effects will be similar each year. Therefore, it doesn’t take a lot into account, but its accuracy can be improved over time through multiple feedback mechanisms. Its website states that Prophet works best with time-series data that has regular seasonality components and lots of historical data to refer to.
Since it is open-source, anyone can download and use Prophet. Check out the link below for more information on Prophet and why you should use to forecast your time-series data:
facebook.github.io
I won’t go into much detail here, but since I did have some issues downloading Prophet for the first time, I’ll explain what I did to install it properly:
Firstly, you can try a simple pip install if that works: pip install fbprophet
However, fbprophet has one major dependency that may cause problems: pystan
Ideally, you want to pip install pystan before fbprophet : https://pystan.readthedocs.io/en/latest/installation_beginner.html
WINDOWS: pystan needs a compiler. Follow instructions here.
The easiest way is to install Prophet in anaconda. Use conda install gcc to set up gcc. Then install Prophet is through conda-forge: conda install -c conda-forge fbprophet
We will be using the following libraries for the tutorial. All basic libraries except Prophet.
import matplotlib.pyplot as pltimport pandas as pdimport numpy as npimport osfrom fbprophet import Prophet
For Dataset, I downloaded almost 10 years of game data for James Harden from here. Essentially, we have data for almost every single game Harden has played (both in the Regular Season, as well as in the Playoffs.)
To use the same data that I’m using, head over to this GitHub page and download the CSV files.
Below, I have imported each of the CSV files containing data on James Harden. ‘19rs’ would be “Regular Season 2019–2020” and ‘18po’ would be “Playoffs 2018–2019” to give you an idea of how each file is named.
harden19rs = pd.read_csv('Downloads/NBAML/harden19rs.csv')harden19po = pd.read_csv('Downloads/NBAML/harden19po.csv')harden18rs = pd.read_csv('Downloads/NBAML/harden18rs.csv')harden18po = pd.read_csv('Downloads/NBAML/harden18po.csv')harden17rs = pd.read_csv('Downloads/NBAML/harden17rs.csv')harden17po = pd.read_csv('Downloads/NBAML/harden17po.csv')harden16rs = pd.read_csv('Downloads/NBAML/harden16rs.csv')harden16po = pd.read_csv('Downloads/NBAML/harden16po.csv')harden15rs = pd.read_csv('Downloads/NBAML/harden15rs.csv')harden15po = pd.read_csv('Downloads/NBAML/harden15po.csv')harden14rs = pd.read_csv('Downloads/NBAML/harden14rs.csv')harden14po = pd.read_csv('Downloads/NBAML/harden14po.csv')harden13rs = pd.read_csv('Downloads/NBAML/harden13rs.csv')harden13po = pd.read_csv('Downloads/NBAML/harden13po.csv')harden12rs = pd.read_csv('Downloads/NBAML/harden12rs.csv')harden12po = pd.read_csv('Downloads/NBAML/harden12po.csv')harden11rs = pd.read_csv('Downloads/NBAML/harden11rs.csv')harden11po = pd.read_csv('Downloads/NBAML/harden11po.csv')harden10rs = pd.read_csv('Downloads/NBAML/harden10po.csv')harden10po = pd.read_csv('Downloads/NBAML/harden10rs.csv')harden09rs = pd.read_csv('Downloads/NBAML/harden09rs.csv')
The next step is super important as we want to make sure our data contains all the required fields or else we wouldn’t be able to do much with it.
Let’s start off by appending each of the CSV files. We can make them chronological as there’s a date component. First, we append each file in the following way:
harden = harden19rs.append(harden19po, ignore_index=True, sort=True)harden = harden.append(harden18rs, ignore_index=True, sort=True)...harden = harden.append(harden09rs, ignore_index=True, sort=True)
Next, I’ll rename some columns so we can understand what they are, and drop the unnecessary columns that aren’t required:
harden = harden.rename(columns={'Unnamed: 7': 'Game', 'MP':'Mins'})#harden['Game'] = pd.concat([harden['Unnamed: 7'].dropna(), harden['Game'].dropna()]).reindex_like(harden)#harden = harden.drop(columns=['Unnamed: 7'])harden = harden.drop(columns=['Unnamed: 5']) harden = harden.drop(columns=['▲'])harden = harden.sort_values(by=['Date'])harden = harden.reset_index(drop=True)
The final dataset looks somewhat like this:
There are some columns in between that we can’t see, but for the most part, it’s all game statistics. Different stats for each game played by James Harden.
Since we want the data to be in chronological order, we set the date as our index value and change the datatype to a pandas datetime variable:
harden.set_index(‘Date’)harden[‘Date’] = pd.to_datetime(harden[‘Date’])
Next, we want to drop any values in our data (even though I doubt we have any). We will use the pandas dropna function and will drop a row in which all values are null. Once we do that, we’ll reset the index to make sure it’s still coherent.
harden = harden.dropna(how='all')harden = harden.reset_index(drop=True)
Next, we have another important step in the data cleanup. During some games, James Harden was inactive, suspended, did not play or did not dress. Certain columns would have these values “suspended” rather than the numeric value that should be there.
Thus, we can remove all the rows (games) in which Harden did not play/dress or was inactive/suspended. However, that might reduce the size of my data by a lot. So instead, I will replace the values with the median of each respective column.
for i in harden:    harden[i] = harden[i].replace('Inactive',     np.median(pd.to_numeric(harden[i], errors='coerce')))    for i in harden:    harden[i] = harden[i].replace('Did Not Play',     np.median(pd.to_numeric(harden[i], errors='coerce')))    for i in harden:    harden[i] = harden[i].replace('Did Not Dress',     np.median(pd.to_numeric(harden[i], errors='coerce')))    for i in harden:    harden[i] = harden[i].replace('Player Suspended',     np.median(pd.to_numeric(harden[i], errors='coerce')))    harden = harden.dropna(how='any')harden = harden.reset_index(drop=True)harden.set_index('Date')
A column containing numeric data should be explicitly assigned that datatype, in order to avoid errors in the future. Therefore, I will assign each column its correct data type. Using downcast='float' for float (decimal) columns.
harden['3P'] = pd.to_numeric(harden['3P'])harden['3PA'] = pd.to_numeric(harden['3PA'])harden['AST'] = pd.to_numeric(harden['AST'])harden['BLK'] = pd.to_numeric(harden['BLK'])harden['DRB'] = pd.to_numeric(harden['DRB'])harden['ORB'] = pd.to_numeric(harden['ORB'])harden['FG'] = pd.to_numeric(harden['FG'])harden['FGA'] = pd.to_numeric(harden['FGA'])harden['PTS'] = pd.to_numeric(harden['PTS'])harden['PF'] = pd.to_numeric(harden['PF'])harden['TOV'] = pd.to_numeric(harden['TOV'])harden['STL'] = pd.to_numeric(harden['STL'])harden['TRB'] = pd.to_numeric(harden['TRB'])harden['3P%'] = pd.to_numeric(harden['3P%'], downcast='float')harden['FG%'] = pd.to_numeric(harden['FG%'], downcast='float')harden['FT%'] = pd.to_numeric(harden['FT%'], downcast='float')harden['GmSc'] = pd.to_numeric(harden['GmSc'], downcast='float')harden['FTA'] = pd.to_numeric(harden['FTA'])harden['FT'] = pd.to_numeric(harden['FT'])print(harden.dtypes)Output: 3P               int643P%            float323PA              int64AST              int64BLK              int64DRB              int64Date    datetime64[ns]FG               int64FG%            float32FGA              int64FT               int64FT%            float32FTA              int64GmSc           float32ORB              int64Opp             objectPF               int64PTS              int64STL              int64TOV              int64TRB              int64dtype: object
Now to the best part. We will forecast any column in this dataset using Prophet. The output will give us an image (graph) of the forecast. In order to create the graph, we need to first fit the Prophet model to our dataset. We will separate the column we want, along with the date column. In this case, I’m predicting points, so I’ll take the ‘PTS’ and ‘Date’ columns.
df = harden[['PTS', 'Date']]
These next steps are quite important, as we will be fitting the prophet model to our data. We want to rename our columns to ‘ds’ (date) and ‘y’ (target). We then define our Prophet model with any given interval_width. Then we fit our Prophet model to the dataset with the date and target variable.
jh = df.rename(columns={'Date': 'ds', 'PTS': 'y'})jh_model = Prophet(interval_width=0.95)jh_model.fit(jh)
To forecast values, we use the make_future_dataframe function, specify the number of periods, frequency as ‘MS’, which is Multiplicative Seasonality.We then create our matplotlib figure for the forecast. The image below the code shows you the output.
jh_forecast = jh_model.make_future_dataframe(periods=36, freq='MS')jh_forecast = jh_model.predict(jh_forecast)plt.figure(figsize=(18, 6))jh_model.plot(jh_forecast, xlabel = 'Date', ylabel = 'PTS')plt.title('James Harden Points')
Now, what if I wanted to compare James Harden’s forecast to Giannis Antetokounmpo, LeBron James, and Kawhi Leonard.
To do this, I’ll take all of the pre-processing and put it in a single function, so I can pre-process the data without repeating lines of code. Below, I’ve supplied a gist that contains the required functions.
Once you have the data processed, we can start merging all the frames and graph them as below.
harden = harden.rename(columns={'Date': 'ds', 'PTS': 'y'})jh_model = Prophet(interval_width=0.95)jh_model.fit(harden)lebron = lebron.rename(columns={'Date': 'ds', 'PTS': 'y'})lj_model = Prophet(interval_width=0.95)lj_model.fit(lebron)giannis = giannis.rename(columns={'Date': 'ds', 'PTS': 'y'})ga_model = Prophet(interval_width=0.95)ga_model.fit(giannis)kawhi = kawhi.rename(columns={'Date': 'ds', 'PTS': 'y'})kl_model = Prophet(interval_width=0.95)kl_model.fit(kawhi)jh_forecast = jh_model.make_future_dataframe(periods=36, freq='MS')jh_forecast = jh_model.predict(jh_forecast)lj_forecast = lj_model.make_future_dataframe(periods=36, freq='MS')lj_forecast = lj_model.predict(lj_forecast)ga_forecast = ga_model.make_future_dataframe(periods=36, freq='MS')ga_forecast = ga_model.predict(ga_forecast)kl_forecast = kl_model.make_future_dataframe(periods=36, freq='MS')kl_forecast = kl_model.predict(kl_forecast)hardens = ['harden_%s' % column for column in jh_forecast.columns]lebrons = ['lebron_%s' % column for column in lj_forecast.columns]kawhis = ['kawhi_%s' % column for column in kl_forecast.columns]gianniss = ['giannis_%s' % column for column in ga_forecast.columns]merge_jh_forecast = jh_forecast.copy()merge_lj_forecast = lj_forecast.copy()merge_kl_forecast = kl_forecast.copy()merge_ga_forecast = ga_forecast.copy()merge_jh_forecast.columns = hardensmerge_lj_forecast.columns = lebronsmerge_kl_forecast.columns = kawhismerge_ga_forecast.columns = giannissforecast = pd.merge(merge_jh_forecast, merge_lj_forecast,                     how = 'inner', left_on='harden_ds', right_on='lebron_ds')forecast = pd.merge(forecast, merge_kl_forecast,                     how = 'inner', left_on='lebron_ds', right_on='kawhi_ds')forecast = pd.merge(forecast, merge_ga_forecast,                     how = 'inner', left_on='kawhi_ds', right_on='giannis_ds')forecast = forecast.rename(columns={'harden_ds': 'Date'})forecast.head()plt.figure(figsize=(10, 7))plt.plot(forecast['Date'], forecast['harden_yhat'], 'b-')plt.plot(forecast['Date'], forecast['lebron_yhat'], 'r-')plt.plot(forecast['Date'], forecast['kawhi_yhat'], 'g-')plt.plot(forecast['Date'], forecast['giannis_yhat'], 'y-')plt.legend(['Harden', 'Lebron', 'Kawhi', 'Giannis'])plt.xlabel('Date')plt.ylabel('Points')plt.title('Harden vs LeBron vs Giannis vs Kawhi');plt.show()
The final output should look like this:
Looks like James Harden has everyone beat when it comes to points per game, although Giannis has some seasonal spikes (maybe he performs well on Fridays). For next time, I might write another tutorial, comparing more players with a lot more specificity. Let me know in the comments if you have any feedback/ideas.
Thanks for the read! Hope you learned something useful. Follow Me for more content on Data Science!