Let’s explore linear regression using a familiar example dataset of student grades. Our goal will be to train a model to predict a student’s grade given the number of hours they have studied.
In this implementation, we will use the statsmodels package to achieve this.
Data Loading
Loading the data:
Code
from pandas import read_csvrepo_url ="https://raw.githubusercontent.com/prof-rossetti/python-for-finance"request_url =f"{repo_url}/main/docs/data/grades.csv"df = read_csv(request_url)df.head()
Name
StudyHours
Grade
0
Arun
10.00
50.0
1
Sofia
11.50
50.0
2
Hassan
9.00
47.0
3
Zara
16.00
97.0
4
Liam
9.25
49.0
Dropping null values:
df.dropna(inplace=True)df.tail()
Name
StudyHours
Grade
17
Tariq
6.0
35.0
18
Lakshmi
10.0
48.0
19
Maya
12.0
52.0
20
Yusuf
12.5
63.0
21
Zainab
12.0
64.0
Exploring relationship between variables:
import plotly.express as pxpx.scatter(df, x="StudyHours", y="Grade", height=350, title="Relationship between Study Hours and Grades", trendline="ols", trendline_color_override="red",)
Data Splitting
X/Y Split
Identifying the dependent and independent variables:
x = df["StudyHours"]print(x.shape)y = df["Grade"]print(y.shape)
(22,)
(22,)
Adding Constants
Note
When using statsmodels, the documentation instructs us to manually add a column of ones (to help the model perform calculations related to the y-intercept):
import statsmodels.api as smx = sm.add_constant(x) # adding in a column of constants, as per the OLS docsx.head()
const
StudyHours
0
1.0
10.00
1
1.0
11.50
2
1.0
9.00
3
1.0
16.00
4
1.0
9.25
Train Test Split
Now we split the training and test sets:
from sklearn.model_selection import train_test_splitx_train, x_test, y_train, y_test = train_test_split(x, y, random_state=99)print("TRAIN:", x_train.shape, y_train.shape)print("TEST:", x_test.shape, y_test.shape)
TRAIN: (16, 2) (16,)
TEST: (6, 2) (6,)
Model Selection and Training
Selecting a linear regression (OLS) model, and training it on the training data to learn the ideal weights:
import statsmodels.api as smmodel = sm.OLS(y_train, x_train, missing="drop")print(type(model))results = model.fit()print(type(results))
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/scipy/stats/_axis_nan_policy.py:418: UserWarning:
`kurtosistest` p-value may be inaccurate with fewer than 20 observations; only n=16 observations were given.
Interpreting P-values
In a regression analysis, each coefficient (the number associated with a feature in the model) has a corresponding t-statistic that tests whether the coefficient is meaningfully different from zero.
Interpreting the results:
T-statistic: Measures how many standard deviations the coefficient is away from zero. A larger t-statistic suggests that the coefficient is far from zero and potentially significant.
P-value (P>|t|): This tells you the probability that the observed t-statistic would occur if the coefficient were actually zero (the null hypothesis). If this probability is very small (typically < 0.05), it means it’s unlikely that the coefficient is zero, suggesting it is statistically significant.
Interpreting p-values:
A low p-value (typically less than 0.05) suggests that you can reject the null hypothesis, meaning the coefficient is statistically significant and likely has an impact on the dependent variable.
A high p-value (greater than 0.05) indicates that the coefficient is not statistically significant, implying that the feature may not contribute meaningfully to the model.
The training results contain an r-squared score, however this represents the error for the training data. To get the real results of how the model generalizes to the test data, we will calculate the r-squared score and other metrics on the test results later.
The part of the training results we care about are the the learned weights (i.e. coefficients), which we use to arrive at the line of best fit:
The training results also contain the fittedvalues (predictions), as well as the resid (residuals or errors). We can compare each of the predicted values against the actual known values, to verify the residuals for illustration purposes:
from pandas import DataFrame# get all rows from the original dataset that wound up in the training set:training_set = df.loc[x_train.index].copy()# create a dataset for the predictions and the residuals:training_preds = DataFrame({"Predictions": results.fittedvalues,"Residuals": results.resid})# merge the training set with the results:training_set = training_set.merge(training_preds, how="inner", left_index=True, right_index=True)# calculate error for each datapoint:training_set["My Error"] = training_set["Grade"] - training_set["Predictions"]training_set
Name
StudyHours
Grade
Predictions
Residuals
My Error
15
Anika
8.00
27.0
32.985616
-5.985616
-5.985616
0
Arun
10.00
50.0
45.713067
4.286933
4.286933
18
Lakshmi
10.00
48.0
45.713067
2.286933
2.286933
13
Mei
8.00
15.0
32.985616
-17.985616
-17.985616
12
Priya
9.00
37.0
39.349341
-2.349341
-2.349341
20
Yusuf
12.50
63.0
61.622380
1.377620
1.377620
7
Kwame
9.00
42.0
39.349341
2.650659
2.650659
21
Zainab
12.00
64.0
58.440518
5.559482
5.559482
16
Elif
9.00
36.0
39.349341
-3.349341
-3.349341
5
Xia
1.00
3.0
-11.560462
14.560462
14.560462
4
Liam
9.25
49.0
40.940273
8.059727
8.059727
19
Maya
12.00
52.0
58.440518
-6.440518
-6.440518
9
Takumi
14.50
74.0
74.349831
-0.349831
-0.349831
8
Fatima
8.50
26.0
36.167479
-10.167479
-10.167479
3
Zara
16.00
97.0
83.895419
13.104581
13.104581
1
Sofia
11.50
50.0
55.258655
-5.258655
-5.258655
It is possible to calculate the training metrics ourselves, to verify the regression results summary we saw above:
When we use the summary_frame method on prediction results, it returns a DataFrame with several columns. Here’s a breakdown of what they mean:
mean: This is the predicted value for each observation based on the model. It’s essentially the point prediction (ŷ) for the corresponding input.
mean_se: This stands for the standard error of the predicted mean. It measures the uncertainty associated with the predicted value due to sampling variability. A smaller mean_se indicates higher confidence in the predicted mean.
Confidence Interval (mean_ci_lower and mean_ci_upper): Represents the range in which the true mean prediction is likely to lie. For predicting the average value (e.g. “the average apple weight is between 140 and 160 grams”).
Prediction Interval (obs_ci_lower and obs_ci_upper): Represents the range in which an individual new observation is likely to lie. For predicting the range where individual values could fall (e.g. “an individual apple might weigh between 120 and 180 grams”).
Merging the actual values in, so we can compare predicted values vs actual values: