0
$\begingroup$

I am having trouble with this segmented regression as it requires two constraints and so far I have only treated single constraints.

Here is an example of some data I am trying to fit:

library(segmented)
library("readxl")
library(ggplot2)

#DATA PRE-PROCESSING
yields <- c(-0.131, 0.533, -0.397, -0.429, -0.593, -0.778, -0.92, -0.987, -1.113,       -1.314, -0.808, -1.534, -1.377, -1.459, -1.818, -1.686, -1.73, -1.221, -1.595, -1.568, -1.883, -1.53, -1.64, -1.396, -1.679, -1.782, -1.033, -0.539, -1.207, -1.437, -1.521, -0.691, -0.879, -0.974, -1.816, -1.854, -1.752, -1.61, -0.602, -1.364, -1.303, -1.186, -1.336)
maturities <- c(2.824657534246575, 2.9013698630136986, 3.106849315068493, 3.1534246575342464, 3.235616438356164, 3.358904109589041, 3.610958904109589, 3.654794520547945, 3.778082191780822, 3.824657534246575, 3.9013698630136986, 3.9863013698630136, 4.153424657534247, 4.273972602739726, 4.32054794520548, 4.654794520547945, 4.778082191780822, 4.986301369863014, 5.153424657534247, 5.32054794520548, 5.443835616438356, 5.572602739726028, 5.654794520547945, 5.824425480949174, 5.941911819746988, 6.275245153080321, 6.4063926940639275, 6.655026573845348, 6.863013698630137, 7.191780821917808, 7.32054794520548, 7.572602739726028, 7.693150684931507, 7.901369863013699, 7.986301369863014, 8.32054794520548, 8.654794520547945, 8.986301369863014, 9.068493150684931, 9.32054794520548, 9.654794520547945, 9.903660453626769, 10.155026573845348)

 off_2 <- 2.693277939965566
 off_10 <- 10.655026573845348

 bond_data = data.frame(yield_change = yields, maturity = maturities) code here

I am trying to fit a segmented model (with a formula of "yield_change~maturity") that has the following constraints:

  1. At maturity = 2 I want the yield_change to be zero
  2. At maturity = 10, I want the yield_change to zero
  3. I want breakpoints(fixed in x) at the 3, 5 and 7-year maturity values.
  4. The off_2 and off_10 variables are the offsets I must use (to set the yields to zero at the 2 and 10-year mark)

As I mentioned before, my previous regressions only required one initial constraint, having one offset value I had to use, I did the following:

  1. I subtracted the offset value from the maturity vector (for example I had maturity = c(10.8,10.9,11,14,16,18, etc... then subtracted the offset, always lower than the initial vector value, 10,4 for example and then fitted a lm with an origin constraint)
  2. From there I could use the segmented package and fit as many breakpoints as I wanted)
  3. As the segmented() function requires a lm object as an input that was possible.

However in this case I cannot to the previous approach as I have two offsets and cannot subtract all the values by off_2 or off_10 as it would fix one point at the zero and not the other.

What I have tried doing is the following:

Separate the dataset into maturities below 5 and maturities over 5 (and essentially apply a segmented model to each of these (with only one breakpoint being 3 or 7).

The issue is that I need to have the 5 year point yield the same for the two models.

I have done this:

bond_data_sub5 <- bond_data[bond_data$maturity < 5,]
bond_data_over5 <- bond_data[bond_data$maturity > 5,]

bond_data_sub5["maturity"] <- bond_data_sub5$maturity - off_2

#2 to 5 year model
model_sub5 <- lm(yield_change~maturity+0, data = bond_data_sub5)

plot(bond_data_sub5$maturity,bond_data_sub5$yield_change, pch=16, col="blue",
     xlab = "maturity",ylab = "yield_change", xlim = c(0,12))

abline(model_sub5) 

Which gives me the following graph:

sub_5 model line with offset maturities

The fact that the maturities have an offset of off_2 is not a problem as when I input my predictions to the function I will create, I will then subtract them by off_2.

The worrying thing is that the 5-year prediction is not at all close to where the actual 5 year should be. Looking at the scatter plot of all maturities we can see this:

five_yr_yield <- predict(model_sub5,data.frame(maturity = 5 - off_2))
plot(bond_data$maturity,bond_data$yield_change, pch=16, col="blue",
 xlab = "maturity",ylab = "yield_change", xlim = c(0,12), ylim  = c(-3,0.5))
points(5,five_yr_yield, pch=16, col = "red")

Gives:

model_sub5 five year red in red

The issue with this method is that if I set the model_sub5 5-year prediction as the beginning constraint of model_over5, I will have the exact same problem I am trying to resolve, two constraints in one lm (but this time (5,five_yr_yield) and (10,0) constraints.

Isn't there a way to fit a lm with no slope and zero as an intercept from (2,0) to (10,0) and then apply the segmented function with breakpoints at 3,5 and 7?

If that isn't possible how would I make the logic I am trying to apply work? Or is there another way of doing this?

If anyone has any suggestions I would greatly appreciate them!

$\endgroup$

1 Answer 1

0
$\begingroup$
  1. At maturity = 2 I want the yield_change to be zero

  2. At maturity = 10, I want the yield_change to zero

  3. I want breakpoints(fixed in x) at the 3, 5 and 7-year maturity values.

I didn't understand the part with the offsets.

I would just define the sum of squares and minimize it:

fun <- function(x, par) {
  y <- x * 0
  y[x > 2] <- par[[1]] * (x[x > 2] - 2) / (3 - 2)
  y[x > 3] <- par[[1]] + (par[[2]] - par[[1]]) / (5 - 3) * (x[x > 3] - 3)
  y[x > 5] <- par[[2]] + (par[[3]] - par[[2]]) / (7 - 5) * (x[x > 5] - 5)
  y[x > 7] <- par[[3]] + (0 - par[[3]]) / (10 - 7) * (x[x > 7] - 7)
  y[x > 10] <- 0
  y
}

yields <- c(-0.131, 0.533, -0.397, -0.429, -0.593, -0.778, -0.92, -0.987, -1.113,       -1.314, -0.808, -1.534, -1.377, -1.459, -1.818, -1.686, -1.73, -1.221, -1.595, -1.568, -1.883, -1.53, -1.64, -1.396, -1.679, -1.782, -1.033, -0.539, -1.207, -1.437, -1.521, -0.691, -0.879, -0.974, -1.816, -1.854, -1.752, -1.61, -0.602, -1.364, -1.303, -1.186, -1.336)
maturities <- c(2.824657534246575, 2.9013698630136986, 3.106849315068493, 3.1534246575342464, 3.235616438356164, 3.358904109589041, 3.610958904109589, 3.654794520547945, 3.778082191780822, 3.824657534246575, 3.9013698630136986, 3.9863013698630136, 4.153424657534247, 4.273972602739726, 4.32054794520548, 4.654794520547945, 4.778082191780822, 4.986301369863014, 5.153424657534247, 5.32054794520548, 5.443835616438356, 5.572602739726028, 5.654794520547945, 5.824425480949174, 5.941911819746988, 6.275245153080321, 6.4063926940639275, 6.655026573845348, 6.863013698630137, 7.191780821917808, 7.32054794520548, 7.572602739726028, 7.693150684931507, 7.901369863013699, 7.986301369863014, 8.32054794520548, 8.654794520547945, 8.986301369863014, 9.068493150684931, 9.32054794520548, 9.654794520547945, 9.903660453626769, 10.155026573845348)

sse <- function(par, x, y) {
  sum((y - fun(x, par))^2)
}

plot(maturities, yields, xlim = c(1, 11))

pars <- optim(c(-1, -1.5, -0.5), sse, x = maturities, y = yields)$par
#[1] -0.4021607 -1.7557449 -1.5516413

curve(fun(x, pars),  col = "dark red", from = 1, to = 11, add = TRUE)
abline(0, 0, lty = 2)
abline(v = 2, lty = 2)
abline(v = 10, lty = 2)

resulting plot showing the data and fit

$\endgroup$
2
  • $\begingroup$ Thank you so much this is really helpful! However, if I want to change the condition of having fixed breaks in x (3,5,7) how would I proceed? Can I also use optim to optimise the new break positions in x? @Roland $\endgroup$
    – leonardo
    Commented Dec 7, 2022 at 13:50
  • $\begingroup$ You could try implementing the x positions of the breakpoints as additional parameters. However, starting values will become very important and depending on the data there could easily be issues with the optimization. $\endgroup$
    – Roland
    Commented Dec 7, 2022 at 17:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.