How to find and correct y-axis offset in time series? I have usage data of many machines like this:

In this machine, the usage grows linear with the time. However, in early 2016, someone or something messed with the total usage in hours and the next measurement dropped down to about 2500. From there, the slope is equal to what it was before.
I'd like to automatically find these changes and correct them, i.e. pull the left "half" of the data down, or the right "half" of the data up, so that the data follows a line, as straight as possible. Are there well-known methods for this?
So far I've found the keyword 'structural change' and the R package strucchange, but that seems a bit too complicated for my case. I essentially have two linear models with the same slope of $\beta$, but different intercepts.
 A: If the data is as small and as simple as shown in your graphic, you can probably also use any ad hoc approach for finding the timing of the drop and its size. If you want to use strucchange it might be a bit "high-tech" but should also get the job done.
For illustration I use the following "regular" ts time series. Your data does not seem to be regular as you have plotted it but maybe you can easily get usage per month or per quarter (as in my example below). This might facilitate the analysis. My data has a slope of 3000 and a drop by 2000 in early 2016:
set.seed(1)
time <- seq(2015, 2017.5, by = 0.25)
hours <- 0 + 3000 * (time - 2015) - 2000 * (time > 2016.125) +
  rnorm(length(time), sd = 40)
usage <- ts(cbind(hours, time), start = 2015, freq = 4)

Visualizing either the total hours or their diffs/growth gives:
plot(usage[,"hours"], type = "o", main = "total")
plot(diff(usage[,"hours"]), type = "o", main = "diff")


A very simple naive approach could be look at the difference between a robust location estimate of the growth (e.g., the median) and the minimum:
median(diff(usage[, "hours"])) - min(diff(usage[, "hours"]))
## [1] 2047.766

Note that if your data is not regular/equidistant then you need to look at hours diffs per time diffs.
If you want to fit the breakpoints in a linear regression model you can easily do so with breakpoints() from strucchange. Here, I set the minimum segment size to 3 and the maximum number of breaks to 1 because this is what we are looking for:
library("strucchange")
bp <- breakpoints(hours ~ I(time - 2015), data = usage, h = 3, breaks = 1)
breakdates(bp)
## [1] 2016
coef(bp)
##                   (Intercept) I(time - 2015)
## 2015(1) - 2016(1)   -21.41769       3053.177
## 2016(2) - 2017(3) -2063.59053       3041.693

This does find a single breakpoint in 2016, i.e., the change occurs between 2016(1) and 2016(2). Note that breakpoints() also allows the slopes to be different. The estimate for the drop is the difference of the intercepts:
coef(bp)[1,1] - coef(bp)[2,1]
[1] 2042.173

If you want to force the slopes to be identical you can also re-estimate such a single-slope model with lm():
m <- lm(hours ~ breakfactor(bp) + I(time - 2015), data = usage)
coef(m)
##             (Intercept) breakfactor(bp)segment2          I(time - 2015) 
##               -17.76357             -2053.65723              3045.86872 

So you see that the three methods lead to slightly different estimates of the drop but are very similar.
Comments:


*

*If your data are not a regular time series than the nice labeling of the time spans and the breakdates() will not work. But everything else should be unchanged.

*From your figure the growth does not seem to be completely linear but possibly exponential. In that case using log(hours) rather than hours would be the natural solution.
A: The problem with the structural change program is that it assumes that you know how many "lines" or connected points you wish to fit and even more importantly it begs the question of auto-correlation and finally it assumes no pulses or seasonal pulses e.g. a july effect. I "roughly eyeballed your data" and came up with 11 observations. My pseud0-data  suggests am ARIMA model (1,1,0) containing first differences and two pulses effectively dealing with an intercept change not a slope change. and  . Note the confidence limits reflect that the model residuals were not normal and outliers/pulses are considered possible in the future. In closing I was a developer for the forecasting package used to automatically perform this tour de force.
Your "simple problem" looking for a "simple solution" that works in all cases is not so simple and there are a lot of bad solutions looking for a place to roost. The "problem" with struct change is not that it is too complex it is too simple. 
@whuber
I see (nearly) equal steps (intervals) between the 11 points. Since I am not comprehending your precise meaning ...can you elaborate and I as usual will try to answer your reflection.

