DATA 624: HOMEWORK 3

Author

Pascal Hermann Kouogang Tafo

Loading the fpp3 library

library(fpp3)
Warning: package 'fpp3' was built under R version 4.5.3
── Attaching packages ──────────────────────────────────────────── fpp3 1.0.3 ──
✔ tibble      3.3.1     ✔ tsibble     1.2.0
✔ dplyr       1.2.1     ✔ tsibbledata 0.4.1
✔ tidyr       1.3.2     ✔ ggtime      1.0.0
✔ lubridate   1.9.5     ✔ feasts      0.5.0
✔ ggplot2     4.0.3     ✔ fable       0.5.0
Warning: package 'tibble' was built under R version 4.5.2
Warning: package 'dplyr' was built under R version 4.5.3
Warning: package 'tidyr' was built under R version 4.5.2
Warning: package 'lubridate' was built under R version 4.5.2
Warning: package 'ggplot2' was built under R version 4.5.3
Warning: package 'tsibble' was built under R version 4.5.3
Warning: package 'tsibbledata' was built under R version 4.5.3
Warning: package 'ggtime' was built under R version 4.5.3
Warning: package 'feasts' was built under R version 4.5.3
Warning: package 'fabletools' was built under R version 4.5.3
Warning: package 'fable' was built under R version 4.5.3
── Conflicts ───────────────────────────────────────────────── fpp3_conflicts ──
✖ lubridate::date()    masks base::date()
✖ dplyr::filter()      masks stats::filter()
✖ tsibble::intersect() masks base::intersect()
✖ tsibble::interval()  masks lubridate::interval()
✖ dplyr::lag()         masks stats::lag()
✖ tsibble::setdiff()   masks base::setdiff()
✖ tsibble::union()     masks base::union()

Exercise 5.1

Produce forecasts for the following series using whichever of NAIVE(y), SNAIVE(y) or RW(y ~ drift()) is more appropriate in each case:

a) Produce the forecast for the Australian Population (global_economy)

head(global_economy)
# A tsibble: 6 x 9 [1Y]
# Key:       Country [1]
  Country     Code   Year         GDP Growth   CPI Imports Exports Population
  <fct>       <fct> <dbl>       <dbl>  <dbl> <dbl>   <dbl>   <dbl>      <dbl>
1 Afghanistan AFG    1960  537777811.     NA    NA    7.02    4.13    8996351
2 Afghanistan AFG    1961  548888896.     NA    NA    8.10    4.45    9166764
3 Afghanistan AFG    1962  546666678.     NA    NA    9.35    4.88    9345868
4 Afghanistan AFG    1963  751111191.     NA    NA   16.9     9.17    9533954
5 Afghanistan AFG    1964  800000044.     NA    NA   18.1     8.89    9731361
6 Afghanistan AFG    1965 1006666638.     NA    NA   21.4    11.3     9938414
Aus_population <- global_economy |>
  filter(Country== "Australia")

Aus_population |> autoplot(Population)

Aus_population|> 
  model(Naive = NAIVE(Population),
    Drift = RW(Population~ drift())) |>
  forecast(h = 20) |> # we are forecasting 20 observations 
  autoplot(Aus_population) 

Interpretation:

We did not test the seasonal Naive model because the dataset does not exhibit any seasonality. The drift benchmark is the most suitable model because it forecasts an extended line which explicitly shows a clear overall upward trend over time connecting the first observation to the last observation and it is more realistic to the Australian population plot.

b)Produce the forecast for the Bricks (aus_production)

head(aus_production) # we visualize the top rows of the data set.
# A tsibble: 6 x 7 [1Q]
  Quarter  Beer Tobacco Bricks Cement Electricity   Gas
    <qtr> <dbl>   <dbl>  <dbl>  <dbl>       <dbl> <dbl>
1 1956 Q1   284    5225    189    465        3923     5
2 1956 Q2   213    5178    204    532        4436     6
3 1956 Q3   227    5297    208    561        4806     7
4 1956 Q4   308    5681    197    570        4418     6
5 1957 Q1   262    5577    187    529        4339     5
6 1957 Q2   228    5651    214    604        4811     7
Bricks <- aus_production |> 
  filter(!is.na(Bricks)) # we are removing the missing data

Bricks |>
  model(
    Seasonal_naive = SNAIVE(Bricks),
    Naive = NAIVE(Bricks),
    Drift = RW(Bricks ~ drift())) |>
  forecast(h = "5 years") |>  # we forecasts in 5 years ahead
  autoplot(Bricks)

Interpretation:

Quarterly Bricks production shows a strong seasonal patterns. The Seasonal Naive benchmark is the best model for this dataset because it is the only one that takes into account seasonality despite the other two models.

c) Produce the forecast for the NSW Lambs (aus_livestock)

head(aus_livestock) # We visualize the dataset
# A tsibble: 6 x 4 [1M]
# Key:       Animal, State [1]
     Month Animal                     State                        Count
     <mth> <fct>                      <fct>                        <dbl>
1 1976 Jul Bulls, bullocks and steers Australian Capital Territory  2300
2 1976 Aug Bulls, bullocks and steers Australian Capital Territory  2100
3 1976 Sep Bulls, bullocks and steers Australian Capital Territory  2100
4 1976 Oct Bulls, bullocks and steers Australian Capital Territory  1900
5 1976 Nov Bulls, bullocks and steers Australian Capital Territory  2100
6 1976 Dec Bulls, bullocks and steers Australian Capital Territory  1800
NSW_Lambs <- aus_livestock |>
  filter(State== "New South Wales",Animal== "Lambs")

fit_NSW_Lambs <- NSW_Lambs |>
  model(
    Seasonal_naive = SNAIVE(Count),
    Naive = NAIVE(Count),
    Drift = RW(Count ~ drift())) |>
  forecast(h = "3 years") |>  # we forecasts in 5 years ahead since it a monthly interval dataset
  autoplot(NSW_Lambs, level=NULL) # I am removing the prediction interval by setting level to NULL

fit_NSW_Lambs

Interpretation

The monthly lambs in New South Wales shows a clear visible seasonality. So the seasonal naive is the best benchmark for this monthly dataset.

d) Produce the forecast for the Household wealth (hh_budget)

head(hh_budget)
# A tsibble: 6 x 8 [1Y]
# Key:       Country [1]
  Country    Year  Debt    DI Expenditure Savings Wealth Unemployment
  <chr>     <dbl> <dbl> <dbl>       <dbl>   <dbl>  <dbl>        <dbl>
1 Australia  1995  95.7  3.72        3.40   5.24    315.         8.47
2 Australia  1996  99.5  3.98        2.97   6.47    315.         8.51
3 Australia  1997 108.   2.52        4.95   3.74    323.         8.36
4 Australia  1998 115.   4.02        5.73   1.29    339.         7.68
5 Australia  1999 121.   3.84        4.26   0.638   354.         6.87
6 Australia  2000 126.   3.77        3.18   1.99    350.         6.29
fit_hh_budget <- hh_budget |>
  model(
    Seasonal_naive = SNAIVE(Wealth),
    Naive = NAIVE(Wealth),
    Drift = RW(Wealth ~ drift())) |>
  forecast(h = 10) |>  # we forecasts 10 years ahead 
  autoplot(hh_budget, level=NULL) # I am removing the prediction interval by setting level to NULL  
Warning: 4 errors (1 unique) encountered for Seasonal_naive
[4] Non-seasonal model specification provided, use RW() or provide a different lag specification.
fit_hh_budget
Warning: Removed 40 rows containing missing values or values outside the scale range
(`geom_line()`).

Interpretation

The dataset plot does not show any seasonality therefore the seasonal naive is definitely not a good fit. The plot show an overall consistent upward trend which proves that the drift model with its extended upward line is the best model to fit this dataset.

e) Produce the forecast for the Australian takeaway food turnover (aus_retail)

head(aus_retail)
# A tsibble: 6 x 5 [1M]
# Key:       State, Industry [1]
  State                        Industry            `Series ID`    Month Turnover
  <chr>                        <chr>               <chr>          <mth>    <dbl>
1 Australian Capital Territory Cafes, restaurants… A3349849A   1982 Apr      4.4
2 Australian Capital Territory Cafes, restaurants… A3349849A   1982 May      3.4
3 Australian Capital Territory Cafes, restaurants… A3349849A   1982 Jun      3.6
4 Australian Capital Territory Cafes, restaurants… A3349849A   1982 Jul      4  
5 Australian Capital Territory Cafes, restaurants… A3349849A   1982 Aug      3.6
6 Australian Capital Territory Cafes, restaurants… A3349849A   1982 Sep      4.2
tail(aus_retail)
# A tsibble: 6 x 5 [1M]
# Key:       State, Industry [1]
  State             Industry               `Series ID`    Month Turnover
  <chr>             <chr>                  <chr>          <mth>    <dbl>
1 Western Australia Takeaway food services A3349435A   2018 Jul     179.
2 Western Australia Takeaway food services A3349435A   2018 Aug     178.
3 Western Australia Takeaway food services A3349435A   2018 Sep     180.
4 Western Australia Takeaway food services A3349435A   2018 Oct     183.
5 Western Australia Takeaway food services A3349435A   2018 Nov     184.
6 Western Australia Takeaway food services A3349435A   2018 Dec     195.
Takeaway <- aus_retail |>
  filter(Industry == "Takeaway food services") |>
  summarise(Turnover = sum(Turnover))

Takeaway |>
  model(SNAIVE(Turnover),
        Naive = NAIVE(Turnover),
    Drift = RW(Turnover ~ drift())) |>
  forecast(h = "3 years") |>
  autoplot(Takeaway, level=NULL)

Interpretation

The Australian takeaway turnover foods shows a clear visible strong monthly seasonality. Therefore, the seasonal naive is the best benchmark for this series.

Exercise 5.2

Use the Facebook stock price (data set gafa_stock) to do the following:

a) Let’s visualize the gafa_stock dataset and produce a time plot of the series

head(gafa_stock|>
       filter(Symbol=="FB")) # first 6 rows of the Facebook stock price from the gafa_stock tsibble
# A tsibble: 6 x 8 [!]
# Key:       Symbol [1]
  Symbol Date        Open  High   Low Close Adj_Close   Volume
  <chr>  <date>     <dbl> <dbl> <dbl> <dbl>     <dbl>    <dbl>
1 FB     2014-01-02  54.8  55.2  54.2  54.7      54.7 43195500
2 FB     2014-01-03  55.0  55.7  54.5  54.6      54.6 38246200
3 FB     2014-01-06  54.4  57.3  54.0  57.2      57.2 68852600
4 FB     2014-01-07  57.7  58.5  57.2  57.9      57.9 77207400
5 FB     2014-01-08  57.6  58.4  57.2  58.2      58.2 56682400
6 FB     2014-01-09  58.7  59.0  56.7  57.2      57.2 92253300
tail(gafa_stock |>
       filter(Symbol=="FB"))# Last 6 rows of the Facebook stock price from the gafa_stock tsibble.
# A tsibble: 6 x 8 [!]
# Key:       Symbol [1]
  Symbol Date        Open  High   Low Close Adj_Close   Volume
  <chr>  <date>     <dbl> <dbl> <dbl> <dbl>     <dbl>    <dbl>
1 FB     2018-12-21  133.  135.  123.  125.      125. 56901500
2 FB     2018-12-24  123.  130.  123.  124.      124. 22066000
3 FB     2018-12-26  126   134.  126.  134.      134. 39723400
4 FB     2018-12-27  132.  135.  130.  135.      135. 31202500
5 FB     2018-12-28  135.  136.  132.  133.      133. 22627600
6 FB     2018-12-31  134.  135.  130.  131.      131. 24625300
# Let's produce the plot of the Facebook closing stock price from the gafa_stocks time series

gafa_stock |>
  filter(Symbol=="FB")|> 
  autoplot(Close)

b) Let’s produce forecasts using the drift method and plot them.

Since the stock only trade during the working day, we could anticipate the fact that there are some missing Facebook price in our dataset related the weekend and holidays.

In order to avoid any when computing our forecast, we need to re-index the time series by using just trading day as our new index which will help us avoinding the problem of any missing stock price days.

# Let's create our new dataset

FB_stock_new <- gafa_stock |>
                    filter(Symbol=="FB") |> # pulling out just the Facebook data
                    mutate(trading_day= row_number()) |> # we create a new variable that represents the number of rows in the data set 
                    update_tsibble(index=trading_day,regular=TRUE) # we are re-indexing our tsibble with trading_day as our new index.

head(FB_stock_new)
# A tsibble: 6 x 9 [1]
# Key:       Symbol [1]
  Symbol Date        Open  High   Low Close Adj_Close   Volume trading_day
  <chr>  <date>     <dbl> <dbl> <dbl> <dbl>     <dbl>    <dbl>       <int>
1 FB     2014-01-02  54.8  55.2  54.2  54.7      54.7 43195500           1
2 FB     2014-01-03  55.0  55.7  54.5  54.6      54.6 38246200           2
3 FB     2014-01-06  54.4  57.3  54.0  57.2      57.2 68852600           3
4 FB     2014-01-07  57.7  58.5  57.2  57.9      57.9 77207400           4
5 FB     2014-01-08  57.6  58.4  57.2  58.2      58.2 56682400           5
6 FB     2014-01-09  58.7  59.0  56.7  57.2      57.2 92253300           6
tail(FB_stock_new)
# A tsibble: 6 x 9 [1]
# Key:       Symbol [1]
  Symbol Date        Open  High   Low Close Adj_Close   Volume trading_day
  <chr>  <date>     <dbl> <dbl> <dbl> <dbl>     <dbl>    <dbl>       <int>
1 FB     2018-12-21  133.  135.  123.  125.      125. 56901500        1253
2 FB     2018-12-24  123.  130.  123.  124.      124. 22066000        1254
3 FB     2018-12-26  126   134.  126.  134.      134. 39723400        1255
4 FB     2018-12-27  132.  135.  130.  135.      135. 31202500        1256
5 FB     2018-12-28  135.  136.  132.  133.      133. 22627600        1257
6 FB     2018-12-31  134.  135.  130.  131.      131. 24625300        1258
#Let's produce forecasts for facebook closing stock prices using the drift method and plot them

FB_stock_new |>
  model(Drift = RW(Close ~ drift())) |> # we fit the drift model
  forecast(h = 60) |>  # we pick the number of days we want to forecasts ahead
  autoplot(FB_stock_new) +
  labs(
    title = "Facebook Closing Stock Price Forecasts Using the Drift Method",
    x = "Trading_Day",
    y = "Closing Price (USD)"
  )

Interpretation

We observe that the time plot of the Facebook closing stock prices historical data shows an overall upward trend throughout the vast majority of the observed period with a notable dip equivalent to an interesting decline towards the end of the series. The drift forecasts shows a slight upward line trajectory which appears to be consistent with extending the line drawn between the first and last observations.

c) Let’s use some of the other benchmark functions to forecast the same data set. Which do you think is best? Why?

For this time series data set, I am not going to fit the seasonal naive model because the facebook stock closing price does not exhibit seasonality.

# Let's fit the other benchmark forecasting methods and compare them

FB_stock_new |>
  model(
    Mean = MEAN(Close),
    Naive = NAIVE(Close),
    Drift = RW(Close ~ drift())
  ) |>
  forecast(h = 60) |> # we are forecasting 60 trading days 
  autoplot(FB_stock_new) +
  labs(
    title = "Benchmark Forecasts for Facebook Closing Stock Prices",
    x = "Trading_Day",
    y = "Closing Price (USD)"
  )

Interpretation

When comparing the three benchmark function to forecast the Facebook Closing stocks prices, we observe that the mean is not a good model for forecasting stocks prices because it forecasts an horizontal line which does not reflect the recent behavior of the stock price and is not close to the last observed historical movement price.

The naive and drift benchmarks are more reasonable because they both start off where the latest recent observed values and the historical movement of the series is. However, the best benchmark function of our data set will be the Drift because it forecasts an extended line which explicitly shows a clear overall upward trend over time connecting the first observation to the last observation and it is more realistic to the facebook stocks closing plot.

Exercise 5.3

Apply a seasonal naïve method to the quarterly Australian beer production data from 1992. Check if the residuals look like white noise, and plot the forecasts. The following code will help.

Extract data of interest

recent_production <- aus_production |> filter(year(Quarter) >= 1992) # Define and estimate a model fit <- recent_production |> model(SNAIVE(Beer)) # Look at the residuals fit |> gg_tsresiduals()

head(aus_production)
# A tsibble: 6 x 7 [1Q]
  Quarter  Beer Tobacco Bricks Cement Electricity   Gas
    <qtr> <dbl>   <dbl>  <dbl>  <dbl>       <dbl> <dbl>
1 1956 Q1   284    5225    189    465        3923     5
2 1956 Q2   213    5178    204    532        4436     6
3 1956 Q3   227    5297    208    561        4806     7
4 1956 Q4   308    5681    197    570        4418     6
5 1957 Q1   262    5577    187    529        4339     5
6 1957 Q2   228    5651    214    604        4811     7
tail(aus_production)
# A tsibble: 6 x 7 [1Q]
  Quarter  Beer Tobacco Bricks Cement Electricity   Gas
    <qtr> <dbl>   <dbl>  <dbl>  <dbl>       <dbl> <dbl>
1 2009 Q1   415      NA     NA   1963       58368   196
2 2009 Q2   398      NA     NA   2160       57471   238
3 2009 Q3   419      NA     NA   2325       58394   252
4 2009 Q4   488      NA     NA   2273       57336   210
5 2010 Q1   414      NA     NA   1904       58309   205
6 2010 Q2   374      NA     NA   2401       58041   236
# extracting the australian production data from 1992 upward

New_production <- aus_production |>
  filter(year(Quarter) >= 1992)
New_production
# A tsibble: 74 x 7 [1Q]
   Quarter  Beer Tobacco Bricks Cement Electricity   Gas
     <qtr> <dbl>   <dbl>  <dbl>  <dbl>       <dbl> <dbl>
 1 1992 Q1   443    5777    383   1289       38332   117
 2 1992 Q2   410    5853    404   1501       39774   151
 3 1992 Q3   420    6416    446   1539       42246   175
 4 1992 Q4   532    5825    420   1568       38498   129
 5 1993 Q1   433    5724    394   1450       39460   116
 6 1993 Q2   421    6036    462   1668       41356   149
 7 1993 Q3   410    6570    475   1648       42949   163
 8 1993 Q4   512    5675    443   1863       40974   138
 9 1994 Q1   449    5311    421   1468       40162   127
10 1994 Q2   381    5717    475   1755       41199   159
# ℹ 64 more rows
# Apply the seasonal naïve method to the quarterly Australian beer in the new production dataset

fit_beer_1992 <- New_production |>
  model(Seaonal_naive=SNAIVE(Beer))

# Let's look at the residuals

fit_beer_1992 |> gg_tsresiduals()
Warning: Removed 4 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 4 rows containing missing values or values outside the scale range
(`geom_point()`).
Warning: Removed 4 rows containing non-finite outside the scale range
(`stat_bin()`).
Warning: Removed 4 rows containing missing values or values outside the scale range
(`geom_rug()`).

Let’s plot the forecasts

# Look a some forecasts

fit_beer_1992 |> forecast() |> autoplot(New_production,level=NULL)

Interpretation

The innovation residuals shows a big change in spread above or below zero and that suggest changing vairance. Looking at the autocorrelation function (acf) graph, we obverse for white noise that the majority of observations fall inside the blue dashed bounds expect from three observations at lag1, lag 3 and especially a significant spike at lag 4. That means the residuals are not pure white noise. The residual histogram is skewed to the left, which means residuals are not normally distributed. The forecast plot is repeating the seasonal pattern but it seems that the decline in beer production is not gradual. We can conclude that although the seasonal naive model capture the seasonality of the new beer production, it fails to do so downward, hence we believe that the forecasts are biased upward.

Exercise 5.4

Repeat the previous exercise using the Australian Exports series from global_economy and the Bricks series from aus_production. Use whichever of NAIVE() or SNAIVE() is more appropriate in each case.

Case of the Australian exports

head(global_economy)
# A tsibble: 6 x 9 [1Y]
# Key:       Country [1]
  Country     Code   Year         GDP Growth   CPI Imports Exports Population
  <fct>       <fct> <dbl>       <dbl>  <dbl> <dbl>   <dbl>   <dbl>      <dbl>
1 Afghanistan AFG    1960  537777811.     NA    NA    7.02    4.13    8996351
2 Afghanistan AFG    1961  548888896.     NA    NA    8.10    4.45    9166764
3 Afghanistan AFG    1962  546666678.     NA    NA    9.35    4.88    9345868
4 Afghanistan AFG    1963  751111191.     NA    NA   16.9     9.17    9533954
5 Afghanistan AFG    1964  800000044.     NA    NA   18.1     8.89    9731361
6 Afghanistan AFG    1965 1006666638.     NA    NA   21.4    11.3     9938414
Aus_exports <- global_economy |> filter(Country == "Australia") 

# fit the naive model

fit_exports <- Aus_exports |> model(NAIVE(Exports))

# Let's look at the residuals

fit_exports |> gg_tsresiduals()
Warning: Removed 1 row containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 1 row containing missing values or values outside the scale range
(`geom_point()`).
Warning: Removed 1 row containing non-finite outside the scale range
(`stat_bin()`).
Warning: Removed 1 row containing missing values or values outside the scale range
(`geom_rug()`).

# Look a some forecasts

fit_exports |> forecast(h = 10) |> autoplot(Aus_exports,level=NULL)

Interpretation

The historical plot shows a strong upward trend but without seasonability that’s why we used naive model.

The ACF shows a significant negative spike at lag 1 so the residuals are not white noise. The residual plot shows that values are hardly centered or distributed at zero. These conditions suggests that the naive do not full capture the trend.

The forecast plot shows a flat line which does not exhibit an upward trend.

we can conclude that the naive model is not the best fit for our data.

Case of the Bricks series from aus_production

head(aus_production)
# A tsibble: 6 x 7 [1Q]
  Quarter  Beer Tobacco Bricks Cement Electricity   Gas
    <qtr> <dbl>   <dbl>  <dbl>  <dbl>       <dbl> <dbl>
1 1956 Q1   284    5225    189    465        3923     5
2 1956 Q2   213    5178    204    532        4436     6
3 1956 Q3   227    5297    208    561        4806     7
4 1956 Q4   308    5681    197    570        4418     6
5 1957 Q1   262    5577    187    529        4339     5
6 1957 Q2   228    5651    214    604        4811     7
# Extracting data of interest

Bricks_prod <- aus_production |> 
  filter(!is.na(Bricks)) # by selecting bricks , we also remove the missing data

# plot the data

Bricks_prod |> autoplot(Bricks)

# fit the seasonal naive model

fit_Bricks_prod <- Bricks_prod |>
  model(Seaonal_naive=SNAIVE(Bricks))


# Let's look at the residuals

fit_Bricks_prod |> gg_tsresiduals()
Warning: Removed 4 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 4 rows containing missing values or values outside the scale range
(`geom_point()`).
Warning: Removed 4 rows containing non-finite outside the scale range
(`stat_bin()`).
Warning: Removed 4 rows containing missing values or values outside the scale range
(`geom_rug()`).

# Look a some forecasts

fit_Bricks_prod |> forecast(h ="3 years") |> autoplot(Bricks_prod,level=NULL)

Interpretation

The quarterly bricks production data plot shows a strong seasonality, therefore the seasonal naive should be appropriate.

The innovation residuals graph shows the bricks volatile which can affect the stability of the variance. Also, the ACF graph shows significant spikes out of the dashed area at seasonal lags w, so the residuals are not white noise. Moreover, the residual histogram is not normally distributed.

The forecast plot is repeating the historical seasonal pattern, but does not capture properly the overall trend downward in the bricks production. We can conclude that the seasonal naive model is biased.

Exercise 5.7

For your retail time series (from Exercise 7 in Section 2.10):

  1. Let’s create a training dataset consisting of observations before 2011
myseries <- aus_retail |>
  filter(`Series ID` == sample(aus_retail$`Series ID`,1))


myseries_train <- myseries |>
  filter(year(Month) < 2011)
  1. Check that your data have been split appropriately by producing the following plot.
autoplot(myseries, Turnover) +
  autolayer(myseries_train, Turnover, colour = "red")

  1. Fit a seasonal naïve model using SNAIVE() applied to your training data (myseries_train).
fit <- myseries_train |>
  model(SNAIVE(Turnover))
  1. check residuals
fit |> gg_tsresiduals()
Warning: Removed 12 rows containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 12 rows containing missing values or values outside the scale range
(`geom_point()`).
Warning: Removed 12 rows containing non-finite outside the scale range
(`stat_bin()`).
Warning: Removed 12 rows containing missing values or values outside the scale range
(`geom_rug()`).

The residuals are not normally distributed because skewed to the right.