Stationarity, autocorrelation, ACF and PACF
Before this lesson: lesson 01 (components, moving averages, lag plots). From the probability-and-statistics track: expectation, variance, covariance and correlation; p-values; the t-test of a regression slope. Time: about 80 minutes, plus the notebook.
Why this matters #
- Model choice. AR, MA, ARMA and ARIMA models (lessons 05 and 06) assume a stationary series after differencing. The ACF and PACF tell you which model to try.
- Residual checks. After every model in this track, you check that the residuals look like white noise: an ACF inside the band and a large Ljung–Box p-value. If not, the model missed a pattern.
- Features. Lags with large autocorrelation are the most useful inputs for machine-learning forecasters (lesson 08).
- Correct statistics. Autocorrelated data contain less information than the same number of independent points. This matters for confidence intervals, for the effective sample size of MCMC chains, and for regressions between trending series, which can be pure nonsense (spurious regression, section 3.9).
1. Predict first #
2. The picture #
White noise and the random walk #
White noise is the simplest series: values with mean 0, the same variance , and no correlation between any two times. It has no memory. A random walk adds up white noise: . It has perfect memory: every shock stays in the series forever. So the random walk wanders further and further away from where it started.
Compare the two processes. The figure draws 50 paths that all start at 0. For the random walk, the spread of the paths at time grows like : the dashed curves are . Move the time slider and compare the variance across the paths with the formula . Switch to the AR(1) process with : the spread grows at first and then stops at . The AR(1) process forgets old shocks; the random walk does not.
Memory that fades: the AR(1) process #
Between "no memory" and "perfect memory" there is the AR(1) process with . Each value keeps a fraction of the previous value and adds a new shock. Old shocks shrink by the factor at every step, so the series always returns towards its mean. Its autocorrelation at lag , the correlation between and , is : it decays geometrically.
Move and predict the ACF before you look. Left: one sample path. Right: the theoretical ACF (stems) and the sample ACF computed from the path (dots), with the grey band . With negative the ACF alternates in sign. Near the path wanders far from 0 for a long time, and on a short path the sample ACF falls faster than . Press Unit root: now there is no theoretical ACF, but the sample ACF still decays slowly. Press New sample several times: the dots move, the stems do not.
Stationary does not mean flat #
The AR(1) path with makes long excursions above and below 0. It is still stationary: the process has the same mean, variance and autocorrelations at every time. A single path can look like it has a trend for a while. Stationarity is a property of the process that generates the data, not of one picture. A random walk is different: its variance really grows with .
The series is , with white noise. Is it weakly stationary?
Weak stationarity needs a constant mean. Here changes with . The variance is constant (), unlike a random walk. Such a series is called trend-stationary: it becomes stationary after you subtract the line.
3. Definitions and main results #
3.1 White noise and the random walk #
Definition (white noise). means , and for . Gaussian white noise is the special case .
Definition (random walk). for , with . With a constant , is a random walk with drift.
Result 1 (variance of a random walk). and . With drift, and the variance is the same.
Proof.
- Unroll the recursion: (plus with drift).
- The mean of a sum is the sum of the means: (or ).
- The are uncorrelated, so the variance of the sum is the sum of the variances: .
The same steps give : the covariance depends on the times themselves, not only on their distance.
3.2 Stationarity #
Definition. A process is strictly stationary if the joint distribution of is the same as that of for every shift and all times. It is weakly stationary (or covariance stationary) if
- is the same for all ;
- ;
- depends only on the lag , not on .
Condition 3 with makes the variance constant as well. A strictly stationary process with finite variance is weakly stationary. For a Gaussian process the two are the same, because means and covariances fix all its joint distributions. In this track "stationary" means weakly stationary.
| Process | Stationary? | Why |
|---|---|---|
| white noise | yes | mean 0, variance , for |
| random walk | no | grows |
| linear trend + noise | no | the mean moves |
| seasonal pattern + noise | no | the mean depends on the season |
| AR(1) with | yes | section 3.3 |
3.3 The AR(1) process #
Definition. with , where is uncorrelated with all past values .
Why it forgets. Insert the equation into itself again and again:
A shock from steps ago has weight . For the weights shrink to 0, and the sum has variance . For the weights stay at 1: this is the random walk, and the sum has no finite variance.
Result 2 (moments of a stationary AR(1)). If and the process is stationary, then
Proof.
- Mean. Take expectations: , so and (because ).
- Variance. is uncorrelated with , so . Stationarity gives , so .
- Autocovariance. Multiply the equation by for and take expectations: . The last term is 0 (the new shock is uncorrelated with the past). So .
- Repeat step 3: , and dividing by gives .
Numbers. For and : and . A simulation of length gives variance 2.77 and sample autocorrelations 0.799, 0.639, 0.511. For : , which alternates in sign.
The case is called a unit root: the characteristic equation has its root at . Lesson 05 generalises this to AR(): a process is stationary when all roots lie outside the unit circle.
A stationary AR(1) process has . What is , the correlation between and ?
, so . An odd power of a negative number is negative: the ACF alternates in sign. Values three steps apart are still (weakly) related through the chain .
3.4 The autocorrelation function and its sample version #
Definition. For a stationary process, the autocovariance function is and the autocorrelation function (ACF) is . It has , and .
Definition (sample ACF). From data with mean :
The sum has only terms, but we divide by . This choice has a reason.
Result 3 (dividing by gives a valid autocovariance). The matrix with entries is positive semidefinite. In particular .
Proof of Result 3
Let for and for all other integers (zero padding). Then . For any vector :
where the last step substitutes and multiplies out the square. A positive semidefinite matrix has minors , which gives .
With the divisor this can fail. For : , the lag-2 sum is , and dividing by gives while . Then "" , which is impossible for a correlation, and the matrix has the negative eigenvalue . The matrix is a Toeplitz matrix (constant along each diagonal; see linear-algebra lesson 19). Lesson 05 solves linear systems with it (the Yule–Walker equations), which needs it to be positive definite.
The white-noise band. If the data are i.i.d. with finite variance, then for large each , , is approximately (Bartlett's formula). So about 95 % of the sample autocorrelations lie inside . This band is for one lag at a time. With 20 lags of true white noise you expect about one value outside the band by chance; with 40 lags, about two. One bar just outside the band at an odd lag is not evidence of anything.
What the sample ACF looks like:
- white noise: all bars (except lag 0) inside the band;
- AR(1) with : geometric decay ; with : alternating signs;
- trend or random walk: very slow, almost linear decay from close to 1. The deviations from come in long runs of one sign, so products at many lags are positive;
- seasonal series: peaks at the lags .
The last two patterns say "not stationary yet: difference first" (section 3.7).
You plot the sample ACF of 500 values of true white noise at lags 1 to 40. How many bars do you expect outside the band ?
Each bar falls outside its 95 % band with probability about 5 %, so the expected count is . The band does not depend on , because the ACF is scale-free. Seeing one or two bars outside is normal; this is a multiple-testing effect.
3.5 The Ljung–Box test #
Instead of looking at each bar, test them together.
Definition (Ljung–Box test). The null hypothesis is that the series is white noise up to lag : . The statistic is
Under the null hypothesis, is approximately with degrees of freedom. A large (a small p-value) means that some autocorrelation is left.
Why . Each is approximately , and for different lags they are approximately independent. A sum of squared independent standard normals is . The factor corrects for small samples; without it you get the older Box–Pierce statistic .
In practice. Use for non-seasonal data and for seasonal data, but not more than (FPP3 §5.4). When you apply the test to the residuals of a fitted ARMA() model, use degrees of freedom, because fitting the model has already removed some autocorrelation (lesson 06).
3.6 The partial autocorrelation function #
In an AR(1) process, and are correlated (), but only through . The PACF removes such indirect effects.
Definition (PACF). The partial autocorrelation at lag , written , is the correlation between and after removing from both the best linear prediction from the values in between, . For there is nothing in between, so .
Result 4 (PACF as a regression coefficient). Let be the best linear predictor of (smallest mean squared error) from the last values of a zero-mean stationary process. Then the last coefficient is the PACF: (Shumway and Stoffer, ch. 3; Brockwell and Davis, ch. 2–3).
So the sample PACF can be computed by regression: for , fit an AR() model by least squares and keep its last coefficient.
Lag 2 by hand. The best coefficients solve the normal equations (the Yule–Walker equations), written with correlations:
By Cramer's rule,
Result 5 (the PACF of an AR process cuts off). For AR(1), and for all . For AR(), for all .
Proof for AR(1), lag 2. , , so . In general: for , the AR() equation is already the best linear predictor from the last values, because the error is uncorrelated with all past values. Its coefficient on is 0.
Move the AR order from 1 to 10. Left: the coefficients of the least-squares AR() fit; the last coefficient is the sample PACF value . The dashed boxes are the true coefficients. Right: the PACF builds up one lag at a time. For AR(2) data, the fitted coefficients match the dashed boxes only from on; at the single coefficient is , not . So is close to the true , and from on the last coefficient stays inside the band. Try the AR(1) data: the PACF cuts off after lag 1.
This gives the classic identification table (lesson 05 explains the MA column):
| ACF | PACF | |
|---|---|---|
| AR() | decays (geometric or damped waves) | cuts off after lag |
| MA() | cuts off after lag | decays |
A stationary AR(1) process has . What is its PACF at lag 2?
is the autocorrelation. The partial autocorrelation removes the effect of : . Once is known, adds nothing.
The Durbin–Levinson recursion
Fitting separate regressions costs a lot. The Durbin–Levinson recursion computes all from in operations. With , for :
For this is exactly the formula above. It solves the Toeplitz Yule–Walker systems of increasing size, reusing the previous solution (Brockwell and Davis, ch. 2). Using the sample ACF in it gives the "Yule–Walker" sample PACF; it differs slightly from the regression version on short series.
3.7 Making a series stationary #
The backshift operator shifts back one step: . The difference is and the seasonal difference is .
- A random walk becomes white noise: (plus with drift).
- A linear trend becomes a constant: .
- A fixed seasonal pattern disappears: .
- Log, then difference ≈ growth rate: , where is the relative change. Take logs first when the variation grows with the level (lesson 01).
- The two differences commute: . Often one seasonal and one ordinary difference are enough.
Over-differencing. Differencing a series that is already stationary does harm.
Result 6. If is white noise, then has and for .
Proof. . . Lags share no , so their covariance is 0. Hence .
So a sample ACF with near after differencing is a sign that you differenced once too often. The same happens when you difference a trend-stationary series : you get . For such a series it is better to subtract the fitted line.
Press the buttons in different orders and watch the ACF. The series is 12 years of a monthly quantity whose seasonal swing grows with its level. Its ACF decays slowly with peaks at lags 12, 24, 36. Try log, then ∇ and ∇₁₂: the ACF falls inside the band and the Ljung–Box test finds no autocorrelation. Now Reset and skip the log: the result is not white noise. Add one more ∇ at the end: jumps to about (over-differencing).
3.8 Unit-root tests: ADF and KPSS #
Looking at plots is not always enough. Two tests are standard, and their null hypotheses are opposite.
- ADF (augmented Dickey–Fuller). Regress (the constant and the trend term are optional). Null hypothesis: a unit root (, so differencing is needed). Alternative: , stationary. The test statistic does not follow a t-distribution under the null; it has its own Dickey–Fuller tables. A small p-value means: reject the unit root.
- KPSS (Kwiatkowski–Phillips–Schmidt–Shin). Null hypothesis: stationary (around a constant, or around a line in the trend version). A small p-value means: not stationary, difference. FPP3 uses KPSS repeatedly to choose the number of differences.
| ADF (null: unit root) | KPSS (null: stationary) | Reading |
|---|---|---|
| small p | large p | stationary |
| large p | small p | unit root: difference |
| small p | small p | conflict: maybe trend-stationary, or a structural break |
| large p | large p | conflict: the data are not informative (short or near a unit root) |
A large p-value is never proof of the null hypothesis. It only says that the data do not contradict it. Short series give large p-values for almost everything. Unit-root tests also have low power against close to 1 (for example 0.95).
An ADF test on a series gives p = 0.4. What can you conclude?
The ADF null hypothesis is "unit root". A large p-value means the data do not contradict this null, but it does not prove it: the test may simply lack power (short series, near 1). Use a KPSS test (opposite null) and the plots together.
3.9 Spurious regression #
Spurious regression (Granger and Newbold, 1974): regress one random walk on another, independent random walk. The true slope is 0, so a 5 % t-test should reject in about 5 % of cases. It rejects far more often.
Run the experiments, then switch to differences. Each experiment draws two independent random walks ( in blue, in grey), regresses on and records the t-statistic of the slope. In levels, the histogram is very wide and most experiments land in the magenta region : the test "finds" a relation that does not exist. Try a larger : it gets worse. With differences, the histogram matches the curve and the rejection rate is near 5 %.
Why it happens. The t-test assumes that the regression errors are stationary and uncorrelated. Here the error is itself a random walk: it is strongly autocorrelated, so the usual standard error is far too small. Phillips (1986) proved that the t-statistic does not converge to a distribution at all; it grows like . So more data make the false result more significant. In simulations with , about 77 % of the experiments reject; with , about 83 %. Warning signs are a high together with strongly autocorrelated residuals.
Fixes. Difference both series (regress on ), add lagged values of both series to the regression, or test for cointegration.
Cointegration
Two non-stationary series and (each with one unit root) are cointegrated if some combination is stationary. Then they share a common stochastic trend, and the levels regression is meaningful: its slope estimate converges even faster than usual. Prices of the same good in two markets, or short and long interest rates, are classic examples. The Engle–Granger test (Engle and Granger, 1987) regresses on and runs a unit-root test on the residuals, with special critical values. Differencing cointegrated series throws away the long-run relation; error correction models keep it.
The periodogram: the ACF in frequency space
For a stationary process with , the spectral density is for (cycles per time step). It says how the variance is spread over frequencies. The ACF and the spectral density are a Fourier pair (the Wiener–Khinchin theorem). White noise has a flat spectrum ("white", like white light); an AR(1) process has , which for is largest at low frequencies ("red noise", the standard null model for climate series).
The sample version is the periodogram at the Fourier frequencies . For it equals exactly , with divided by . A seasonal series with period has a peak at and at its harmonics . The periodogram is not a consistent estimator of (its variance does not shrink with ); smoothing it fixes this (Shumway and Stoffer, ch. 4).
Answers to section 1. (1) , so the standard deviation is 10 (Result 1). (2) Negative: (Result 2). (3) Far more than 5 %: about 77 % of the time for (section 3.9).
4. Worked example #
A short series with (too short for real use, but good for computing by hand):
Step 1: mean and deviations. . The deviations are
Step 2: autocovariances (divide by ).
- Lag 0: , so .
- Lag 1: the 7 products are . Their sum is , so .
- Lag 2: the 6 products are . Their sum is , so .
Step 3: autocorrelations. and .
Step 4: the band. . Both values are inside the band. With only 8 points, even is not significant.
Step 5: PACF at lag 2.
The partial autocorrelation is larger in size than . Once is known, a high points to a lower .
Step 6: Ljung–Box with .
For with 2 degrees of freedom, , so the p-value is . We cannot reject "white noise up to lag 2", which agrees with step 4.
Step 7: an AR(1) check. If this series came from an AR(1) process, we would expect . The estimate has the wrong sign, and is far from 0. So AR(1) is not a good description (the series looks more like a short wave), although 8 points cannot prove it.
5. In code #
The first part checks the worked example and Results 2 and 6. The second part runs ADF and KPSS with statsmodels.
import numpy as np
from scipy import stats
def sample_acf(y, max_lag):
d = np.asarray(y, float) - np.mean(y)
n = len(d)
gamma = np.array([d[: n - k] @ d[k:] / n for k in range(max_lag + 1)]) # divide by n
return gamma / gamma[0]
y = np.array([3, 5, 6, 4, 2, 1, 3, 4.])
r = sample_acf(y, 2)
print(r.round(4)) # [ 1. 0.4583 -0.3333]
print(round((r[2] - r[1]**2) / (1 - r[1]**2), 4)) # -0.6879 (PACF at lag 2)
n, ell = len(y), 2
Q = n * (n + 2) * np.sum(r[1:]**2 / (n - np.arange(1, ell + 1)))
print(round(Q, 3), round(stats.chi2.sf(Q, df=ell), 4)) # 3.882 0.1435
# AR(1) with phi = 0.8: variance and ACF against theory
rng = np.random.default_rng(0)
phi, n = 0.8, 200_000
e = rng.normal(size=n)
x = np.empty(n); x[0] = e[0] / np.sqrt(1 - phi**2) # start in the stationary distribution
for t in range(1, n):
x[t] = phi * x[t - 1] + e[t]
print(round(x.var(), 2), 1 / (1 - phi**2)) # 2.77 2.7777777777777786
print(sample_acf(x, 3)[1:].round(3), phi ** np.arange(1, 4)) # [0.799 0.639 0.511] [0.8 0.64 0.512]
# over-differencing white noise gives rho(1) = -0.5
w = rng.normal(size=100_000)
print(sample_acf(np.diff(w), 1)[1].round(3)) # -0.5
import warnings
import numpy as np
from statsmodels.tsa.stattools import adfuller, kpss # statsmodels 0.15
rng = np.random.default_rng(3)
walk = np.cumsum(rng.normal(size=300)) # unit root
ar = np.zeros(300)
for t in range(1, 300):
ar[t] = 0.5 * ar[t - 1] + rng.normal() # stationary AR(1)
with warnings.catch_warnings():
warnings.simplefilter("ignore") # KPSS warns when p is outside its table
for name, s in [("walk", walk), ("diff(walk)", np.diff(walk)), ("AR(1)", ar)]:
print(f"{name:11s} ADF p = {adfuller(s)[1]:.3f} KPSS p = {kpss(s, nlags='auto')[1]:.3f}")
# walk ADF p = 0.858 KPSS p = 0.010
# diff(walk) ADF p = 0.000 KPSS p = 0.100
# AR(1) ADF p = 0.000 KPSS p = 0.100
statsmodels reports KPSS p-values only between 0.01 and 0.1 (from a table): 0.010 means "0.01 or smaller" and 0.100 means "0.1 or larger". The random walk: ADF cannot reject a unit root and KPSS rejects stationarity, so difference it. After one difference both tests agree that it is stationary.
6. Where it shows up #
- Model identification and residual checks. The ACF/PACF table of section 3.6 is the first step of the Box–Jenkins method (lessons 05 and 06). Every forecasting model in this track ends with an ACF plot and a Ljung–Box test of its residuals.
- Feature selection for ML. Lags with large ACF or PACF values, and the seasonal lags , are the standard lag features of gradient-boosting forecasters (lesson 08). Differencing or scaling the input window is the same idea as making the series stationary; deep forecasters such as RevIN-based models normalise each window for this reason (lesson 09).
- Finance. Daily returns (log-differences of prices) have almost no autocorrelation, but squared returns are strongly autocorrelated: volatility clusters. This is what GARCH models describe (lesson 06).
- MCMC and simulation. The samples of a Markov chain are autocorrelated. The effective sample size uses the ACF of the chain; for an AR(1)-like chain it is about . With , 10 000 draws are worth only about 526 independent ones.
- ML for science. Climate scientists test whether a spectral peak is real against an AR(1) "red noise" background, and use the effective sample size when they compute the significance of trends in autocorrelated data.
- Drift and monitoring. Many drift tests assume independent samples; with autocorrelated metrics they raise false alarms (see the MLOps track).
7. Common mistakes #
- Reading one bar outside the band as a discovery. The band is for one lag at a time. Look for a pattern (decay, cut-off, seasonal peaks) or use the Ljung–Box test.
- Dividing the sample autocovariance by . It can give "correlations" larger than 1 and a matrix that is not positive semidefinite. Divide by .
- "Stationary means no visible pattern." An AR(1) path with looks like it has trends, but it is stationary. A seasonal series looks regular, but it is not stationary.
- Treating a large ADF p-value as proof of a unit root (or a large KPSS p-value as proof of stationarity). Absence of evidence is not evidence. Use both tests and the plots.
- Differencing too often. after differencing means the last difference was not needed.
- Regressing one trending series on another. Two unrelated random walks give a "significant" slope most of the time. Difference both, or test for cointegration.
- Mixing up ACF and PACF. The ACF of an AR(1) process decays; its PACF cuts off after lag 1.
8. Check yourself #
- Random walk with drift: , , . Give the
mean and the variance of .
Answer
and (standard deviation 10). - A stationary AR(1) process has and . What can
be, and what is ?
Answer
gives , so or . In both cases . - You compute the sample ACF of 400 residuals at lags 1 to 20. Where is the band, and how many
bars outside it are normal for white noise?
Answer
. About bar outside the band is normal by chance. - For a stationary process and . Compute . Could the process
be AR(1)?
Answer
. For AR(1) we would need and , so this is not AR(1). - After one difference, the sample ACF of a long series has and nothing else
outside the band. What happened, and what do you do?
Answer
This is the signature of over-differencing (Result 6: ). The original series was probably already stationary (or trend-stationary). Model the original series, or subtract a fitted trend instead of differencing. - ADF gives p = 0.01 and KPSS gives p = 0.01 on the same series. What could explain it?
Answer
Both nulls are rejected, so the tests conflict. Common causes: a trend-stationary series tested with a KPSS version without a trend, a structural break (a sudden level shift), or changing variance. Look at the plot, try the trend versions of the tests, and consider a model for the break.
Define weak (covariance) stationarity.
Constant mean, finite variance, and depends only on the lag , not on .
What is the variance of a random walk with at time ?
, because is a sum of uncorrelated shocks.
What is the variance of a stationary AR(1) process ?
, from .
What is the ACF of a stationary AR(1) process? Give for .
; for , .
Why does the sample autocovariance divide by and not by ?
With the autocovariance matrix is positive semidefinite, so ; with it can fail.
Where is the approximate 95 % band of a sample ACF for white noise, and how must it be read?
, for one lag at a time: about 1 lag in 20 falls outside by chance.
What does the Ljung–Box test test, and what is its statistic?
The null "no autocorrelation up to lag "; .
What is the PACF at lag ?
The correlation of and after removing the linear effect of ; equal to the last coefficient of the best AR() predictor.
What are the null hypotheses of the ADF and KPSS tests?
ADF: a unit root (not stationary). KPSS: stationary.
What is the sign of over-differencing in the ACF?
close to : differencing white noise gives , with .
9. Interview angle #
- "What is stationarity, and why do we care?" — Constant mean, variance and lag-dependent covariance. Models estimated on the past can only describe the future if these properties stay the same; ARMA theory and most tests assume it.
- "How do you make a series stationary?" — Log or Box–Cox for growing variance, seasonal difference for seasons, ordinary difference for trends or unit roots; check with plots, ACF and KPSS/ADF; do not over-difference.
- "ACF vs PACF: how do you use them?" — The ACF of an AR() decays and its PACF cuts off after ; the reverse for MA().
- "Why can a regression between two time series be misleading?" — Spurious regression: with non-stationary series the t-test is invalid; the t-statistic grows with . Difference, or test for cointegration.
- "Derive the ACF of an AR(1)." — Multiply by , take expectations, use that the shock is uncorrelated with the past: .
10. Practice #
The notebook for this lesson runs in Colab. Each exercise has a check cell and a folded solution:
- Write
sample_acf(y, max_lag)(divide by ) and check it on the worked example. - Write
simulate_ar1(phi, n, seed)that starts in the stationary distribution, and compare its sample variance and ACF with and for several . - Write
pacf_by_regression(y, max_lag)withnp.linalg.lstsq, and check it on AR(1) and AR(2) data. - Write
ljung_box(x, lag)returning and the p-value, and check that p-values of white noise are roughly uniform while AR(1) data give small p-values. - Spurious regression: write
slope_t_stat(x, y)and estimate the rejection rate for 1000 pairs of independent random walks, in levels and in differences.
11. Watch and read #
- Hyndman and Athanasopoulos, Forecasting: Principles and Practice, 3rd ed. (free online): §2.8 autocorrelation and §2.9 white noise, §5.4 residual diagnostics (Ljung–Box), §9.1 stationarity and differencing (KPSS).
- Shumway and Stoffer, Time Series Analysis and Its Applications, 5th ed. (Springer, 2025), chapters 1–3 (stationarity, ACF, PACF, AR models) and 4 (spectral analysis); the R code of the book is free on GitHub.
- Penn State STAT 510, Applied Time Series Analysis (free course notes): lessons 1 and 2 on the ACF, the PACF and AR models.
- Brockwell and Davis, Introduction to Time Series and Forecasting (Springer): Bartlett's formula for the sample ACF, the PACF and the Durbin–Levinson recursion.
- Granger and Newbold (1974), Spurious regressions in econometrics, Journal of Econometrics 2(2) — the original simulation study.
Runs in your browser with nothing to install. The notebook repeats the key demos and has exercises with checks; solutions are inside, folded.
No Google account needed: Python runs inside your browser. The first start downloads about 40 MB; your changes are saved in this browser only.