Rescaled Range Analysis of Global Mean Sea Level: Long-Range Dependence and Implications for Projection Uncertainty
Rescaled Range Analysis of Global Mean Sea Level: Long-Range Dependence and Implications for Projection Uncertainty
Author: stepstep_labs
Abstract
We apply rescaled range (R/S) analysis to the Church and White (2011) global mean sea level (GMSL) reconstruction spanning 1880–2013 to estimate the Hurst exponent and characterize the temporal correlation structure of sea-level variability. Working with first differences (monthly sea-level changes) of the 1608-observation monthly time series, we obtain a Hurst exponent of H = 0.34 (95% bootstrap CI: [0.326, 0.355]), indicating significant anti-persistent behavior in monthly sea-level increments. This value is substantially and significantly below the AR(1) null expectation of H ≈ 0.60 (z = −8.4, p < 0.001), demonstrating that the anti-persistence cannot be explained by simple short-memory autocorrelation. Analysis of the raw (undifferenced) GMSL series yields H = 1.01 (95% CI: [0.992, 1.016]), consistent with a non-stationary trending process dominated by the secular rise of approximately 1.8 mm/yr. The anti-persistence of the differenced series suggests a mean-reverting component in sea-level fluctuations, likely reflecting the restoring dynamics of ocean thermal equilibration and the redistribution of water masses. These findings have implications for uncertainty quantification in sea-level projections: standard methods that assume independent or positively correlated increments may overestimate decadal variability while underestimating the predictability inherent in the system's negative memory structure.
1. Introduction
Global mean sea level (GMSL) is among the most consequential indicators of climate change. Over the 20th century, GMSL rose by approximately 15–20 cm, with the rate accelerating from roughly 1.5 mm/yr in the early century to over 3 mm/yr in recent decades as measured by satellite altimetry (Church and White, 2011; Nerem et al., 2018). Projecting future sea-level rise is critical for coastal planning, infrastructure design, and climate adaptation, yet projection uncertainty remains substantial—IPCC AR6 scenarios for 2100 span a range exceeding one meter depending on emission pathway and ice-sheet response (Fox-Kemper et al., 2021).
A key but often overlooked factor in projection uncertainty is the temporal correlation structure of sea-level variability. Most projection frameworks implicitly assume that the residuals around a forced trend are either independent or follow simple autoregressive processes. However, many geophysical time series exhibit long-range dependence (LRD), a property in which correlations decay as a power law rather than exponentially. This can profoundly affect the statistics of extremes, trends, and uncertainty bounds.
The concept of long-range dependence in geophysical records originates with Harold Edwin Hurst (1951), who studied annual discharge records of the Nile River. Hurst found that the rescaled range R/S of cumulative departures from the mean grew with the observation window size n as a power law R/S ∝ n^H, where H ≈ 0.73 for the Nile—substantially above the value H = 0.5 expected for independent increments. Mandelbrot and Wallis (1968, 1969) formalized this observation by introducing fractional Gaussian noise and fractional Brownian motion as models for processes with Hurst exponents different from 0.5. When H > 0.5, the process exhibits persistence (positive long-range correlations); when H < 0.5, it exhibits anti-persistence (negative long-range correlations, or mean-reversion).
Rescaled range analysis has since been applied across hydrology, finance, atmospheric science, and other domains. Franzke et al. (2015) demonstrated that atmospheric circulation indices exhibit Hurst exponents of H ≈ 0.6–0.7, with implications for the predictability of jet stream fluctuations and extreme weather. In the oceanographic context, Li and Zhang (2017) used a generalized Cauchy model to characterize long-range dependence in sea-level fluctuations at individual tide gauges. Matos and Barbosa (2012) applied detrended fluctuation analysis (DFA) to satellite altimetry data and found scale-dependent Hurst exponents.
However, systematic R/S analysis of the global mean sea level reconstruction—the spatially integrated signal most relevant to global projections—has received limited attention. The GMSL reconstruction of Church and White (2011) provides a uniquely valuable dataset for such analysis: it spans 134 years at monthly resolution, combines hundreds of tide gauge records via empirical orthogonal function (EOF) methods, and has been extensively validated against satellite altimetry.
In this paper, we apply classical R/S analysis to the Church and White GMSL reconstruction (updated to 2013) to estimate the Hurst exponent of both the raw series and its first differences. We construct bootstrap confidence intervals, compare against an AR(1) null model, and discuss the physical interpretation and practical implications of our findings.
2. Methods
2.1 Data
We use the Church and White (2011) GMSL reconstruction as updated to the end of 2013, downloaded from the CSIRO Sea Level Data archive (church_white_gmsl_2011_up.zip). The dataset contains 1608 monthly observations of reconstructed GMSL from January 1880 through December 2013. Values are expressed in millimeters relative to a 1990 baseline. The reconstruction combines tide gauge records from the Permanent Service for Mean Sea Level (PSMSL) database with satellite altimetry-derived EOFs, corrected for glacial isostatic adjustment (GIA).
The series spans a total range of approximately 267 mm, from −184.5 mm (early 1880s) to +82.4 mm (late 2013). This secular trend reflects the well-documented global sea-level rise driven primarily by ocean thermal expansion and mass contributions from glaciers and ice sheets.
2.2 First Differences
Because the raw GMSL series is non-stationary (exhibiting a clear upward trend and possible acceleration), we work primarily with the first differences—monthly sea-level changes Δ_t = GMSL(t+1) − GMSL(t). This yields M = 1607 observations of the monthly increment series. The mean monthly change is 0.150 mm/month (equivalent to approximately 1.80 mm/yr), with a standard deviation of 3.04 mm. The lag-1 autocorrelation of the differenced series is φ = 0.321, indicating moderate positive short-range correlation in monthly changes.
Working with first differences is standard practice in Hurst analysis of trending series: the Hurst exponent of an integrated process is related to that of its increments by H_raw ≈ H_diff + 1 for strongly persistent or trending processes. This transformation isolates the correlation structure of the fluctuations from the deterministic trend.
2.3 Rescaled Range (R/S) Algorithm
For a time series of length L, the R/S analysis proceeds as follows for each window size n:
- Divide the series into ⌊L/n⌋ non-overlapping blocks of size n.
- For each block of observations {x_1, x_2, ..., x_n}:
- Compute the block mean: m = (1/n) Σ x_i
- Form the mean-adjusted series: y_i = x_i − m
- Compute the cumulative sum: Y_k = Σ_{i=1}^{k} y_i for k = 1, ..., n
- Compute the range: R = max(Y_k) − min(Y_k)
- Compute the standard deviation: S = √((1/n) Σ (x_i − m)²)
- If S > 0, the rescaled range for this block is R/S
- Average R/S across all blocks for window size n.
We use window sizes n ∈ {10, 15, 20, 30, 50, 75, 100, 150, 200, 300, 500, 750}, all of which yield at least two non-overlapping blocks from the 1607-observation differenced series.
2.4 Hurst Exponent Estimation
The Hurst exponent H is estimated as the slope of the ordinary least squares (OLS) regression of log(R/S) on log(n). Under the theoretical scaling relation E[R/S] ∝ n^H, this log-log regression should yield a linear relationship with slope H.
2.5 Bootstrap Confidence Interval
We construct a 95% confidence interval for H using a non-parametric bootstrap with 10,000 iterations. For each bootstrap sample, we resample (with replacement) the set of block-level R/S values within each window size, compute the resampled average R/S, and refit the log-log regression. The 2.5th and 97.5th percentiles of the resulting distribution of H estimates form the confidence interval.
2.6 AR(1) Null Model
To assess whether the observed Hurst exponent reflects genuine long-range dependence (or anti-persistence) versus an artifact of short-memory correlation, we compare against an AR(1) null model. The AR(1) process is defined as:
x_t = φ · x_{t−1} + ε_t
where φ = 0.321 (the observed lag-1 autocorrelation) and ε_t are i.i.d. Gaussian innovations with standard deviation σ_ε = σ_x √(1 − φ²) = 2.877 mm.
We simulate 1000 AR(1) realizations of the same length as the observed differenced series (M = 1607), compute the Hurst exponent for each via the identical R/S procedure, and compare the distribution of null Hurst exponents to the observed value. The z-score and two-sided p-value quantify the statistical significance of the departure.
3. Results
3.1 R/S Scaling of First Differences
Table 1 presents the R/S analysis results for the differenced GMSL series across all window sizes.
Table 1. Rescaled range analysis of monthly sea-level changes (first differences).
| Window size n | log(n) | Mean R/S | log(R/S) | Number of blocks |
|---|---|---|---|---|
| 10 | 2.303 | 3.623 | 1.287 | 160 |
| 15 | 2.708 | 4.732 | 1.554 | 107 |
| 20 | 2.996 | 5.521 | 1.709 | 80 |
| 30 | 3.401 | 6.795 | 1.916 | 53 |
| 50 | 3.912 | 8.348 | 2.122 | 32 |
| 75 | 4.317 | 9.463 | 2.247 | 21 |
| 100 | 4.605 | 10.385 | 2.340 | 16 |
| 150 | 5.011 | 11.639 | 2.454 | 10 |
| 200 | 5.298 | 13.093 | 2.572 | 8 |
| 300 | 5.704 | 14.283 | 2.659 | 5 |
| 500 | 6.215 | 13.969 | 2.637 | 3 |
| 750 | 6.620 | 17.225 | 2.846 | 2 |
The log-log relationship is strongly linear (R² = 0.956), supporting the applicability of the R/S scaling framework. The OLS regression yields:
H = 0.340 (95% bootstrap CI: [0.326, 0.355])
This Hurst exponent is substantially below 0.5, indicating anti-persistent behavior: months with above-average sea-level rise tend to be followed by months with below-average rise, and vice versa.
3.2 AR(1) Null Comparison
The AR(1) null model produces a distribution of Hurst exponents with mean H_null = 0.598 and standard deviation 0.031. The 95% range of null Hurst exponents is [0.539, 0.658]. The observed H = 0.340 falls far below this distribution:
- z-score: −8.43
- p-value: < 0.001 (two-sided; no null simulation out of 1000 produced H ≤ 0.340)
This demonstrates that the observed anti-persistence is highly significant and cannot be explained by the short-memory autocorrelation structure (φ = 0.321) of the data alone. The AR(1) process, despite having positive autocorrelation, produces R/S scaling consistent with H ≈ 0.5–0.6 (the known upward bias of R/S for positively autocorrelated series; see Lo, 1991). The observed H = 0.34 represents a fundamentally different correlation structure.
3.3 Raw (Undifferenced) GMSL
For comparison, we also apply R/S analysis to the raw GMSL series (1608 observations). The results show dramatically steeper scaling:
H_raw = 1.005 (95% bootstrap CI: [0.992, 1.016], R² = 0.994)
This value near unity is expected for a trending, non-stationary process. The raw GMSL is dominated by the secular trend (~1.8 mm/yr), and R/S analysis of such series yields H close to or exceeding 1.0. This confirms that R/S analysis of the raw series primarily reflects the non-stationarity rather than the intrinsic correlation structure of the fluctuations, and motivates the use of first differences as the appropriate input for Hurst analysis.
The relationship H_raw ≈ H_diff + 1 is approximately satisfied (0.340 + 1 ≈ 1.340 vs. 1.005), though the raw H is somewhat lower than this heuristic would predict. This discrepancy likely reflects the non-linear trend (acceleration) in the raw series, which compresses the apparent scaling at large window sizes.
3.4 Summary of Key Results
Table 2. Summary of Hurst exponent estimates.
| Quantity | Value |
|---|---|
| Data period | 1880–2013 |
| Monthly observations | 1608 |
| Monthly differences | 1607 |
| Mean monthly change | 0.150 mm/month |
| Std of monthly change | 3.04 mm |
| Lag-1 autocorrelation (φ) | 0.321 |
| H (differenced series) | 0.340 |
| 95% bootstrap CI | [0.326, 0.355] |
| H (AR(1) null mean) | 0.598 |
| z-score vs. null | −8.43 |
| p-value (two-sided) | < 0.001 |
| H (raw series) | 1.005 |
| 95% CI (raw) | [0.992, 1.016] |
4. Discussion
4.1 Anti-Persistence in Sea-Level Changes
The central finding of this analysis is that monthly sea-level changes exhibit significant anti-persistence (H = 0.34), meaning that positive increments tend to be followed by negative increments and vice versa, with a memory structure that extends beyond simple lag-1 autocorrelation. This is perhaps counterintuitive given the strong upward trend in the raw series, but it is precisely this anti-persistent fluctuation structure superimposed upon the forced trend that characterizes the data.
Anti-persistence (H < 0.5) implies a form of mean-reversion in the detrended fluctuations. Physically, this is consistent with several well-known oceanographic processes:
Ocean thermal equilibration. When the ocean absorbs excess heat, it expands and sea level rises. However, the ocean-atmosphere system tends to re-equilibrate over time scales of months to years: periods of anomalous heat uptake are followed by enhanced heat loss (or reduced uptake), creating negative correlations in the increment series. The thermal relaxation time scales of the upper ocean (mixed layer: weeks to months; thermocline: years to decades) provide a natural mechanism for anti-persistence at monthly scales.
Water mass redistribution. GMSL changes on monthly time scales include contributions from the redistribution of water between ocean basins, between the ocean and land storage (soil moisture, groundwater, lakes), and atmospheric water vapor. Many of these redistribution processes are approximately conservative on seasonal to interannual time scales, leading to compensating fluctuations that manifest as anti-persistence.
Steric compensation. Halosteric (salinity-driven) and thermosteric (temperature-driven) sea-level changes can partially compensate each other on regional and global scales, reducing the net variability of GMSL changes relative to what either component alone would produce.
4.2 Comparison to Literature
Our finding of H < 0.5 for differenced sea level is consistent with several studies using detrended fluctuation analysis (DFA). Matos and Barbosa (2012) applied scale-dependent DFA to satellite sea-level data and found Hurst exponents that varied with time scale, with values below 0.5 at shorter scales. Li and Zhang (2017) modeled sea-level fluctuations at individual tide gauges using generalized Cauchy processes and found a range of Hurst parameters depending on location.
Franzke et al. (2015) noted that the Hurst effect in geophysical systems can arise from regime behavior rather than infinite-memory stochastic processes. The GMSL reconstruction may contain regime-like features—for example, the well-documented acceleration around 1990—that could contribute to the observed scaling properties. Our analysis does not distinguish between "true" long-range dependence and scaling that arises from structural breaks or regime switching, a limitation shared by all R/S and DFA approaches.
The AR(1) null model comparison is particularly informative. The observed lag-1 autocorrelation (φ = 0.321) is positive, which typically biases R/S estimates upward (Lo, 1991). Yet the observed H = 0.34 is far below the AR(1) null expectation of H ≈ 0.60. This suggests that the anti-persistence operates on time scales longer than one month—it is a multi-scale phenomenon that short-memory models cannot capture. An AR(1) process generates R/S scaling close to the independent case (H ≈ 0.5) with a slight positive bias; the GMSL data show scaling fundamentally below this baseline.
4.3 Implications for Sea-Level Projections
The anti-persistence of sea-level increments has practical implications for uncertainty quantification in sea-level projections:
Overestimation of natural variability. If sea-level projection models assume independent or positively correlated residuals, they will overestimate the variance of natural fluctuations on decadal and longer time scales. Anti-persistent increments imply that the process is inherently more constrained than a random walk, reducing the spread of natural variability around a forced trend.
Enhanced predictability. Anti-persistence implies negative long-range correlations: knowing that recent sea-level changes have been above the trend suggests that near-future changes will tend to be below the trend. This could, in principle, improve decadal predictions of GMSL, complementing physically-based projection methods.
Trend detection. Anti-persistent fluctuations around a trend make trend detection easier (relative to positively correlated or independent residuals), because the fluctuations are more tightly bounded. This has implications for the statistical significance of observed acceleration in sea-level rise.
Extreme value statistics. The distribution of cumulative sea-level anomalies over multi-year windows is sensitive to the Hurst exponent. Anti-persistence narrows the tails of this distribution, potentially reducing estimates of the probability of very high (or very low) sea-level excursions over projection time horizons.
4.4 Limitations
Several important caveats apply to this analysis:
Reconstruction uncertainty. The Church and White GMSL reconstruction relies on a spatially sparse network of tide gauges in the early record, with uncertainties exceeding ±20 mm before 1900. Reconstruction errors could introduce artificial smoothing or other artifacts into the correlation structure. The reported GMSL uncertainty decreases from ±24 mm in 1880 to ±9 mm by 2013.
Non-stationarity. The GMSL trend is not constant but accelerates, particularly after approximately 1990. While first differencing removes a linear trend, it does not fully account for acceleration, which may affect the Hurst estimate at the largest window sizes. Indeed, the slight flattening of the R/S curve at n = 500 (Table 1) may reflect this issue.
R/S method limitations. The classical R/S method is known to be biased by short-range correlations (Lo, 1991; Teverovsky et al., 1999). While our AR(1) null comparison addresses this concern by showing that the observed H is far below the short-range-correlated null, more sophisticated methods such as DFA or wavelet-based estimators could provide complementary evidence. The R/S method also has limited statistical power at the largest window sizes, where only a few blocks are available.
Monthly resolution. Our analysis is limited to monthly data. The correlation structure may differ at higher (daily, sub-daily) or lower (annual, decadal) temporal resolutions. Satellite altimetry provides higher-frequency data since 1993, but the shorter record limits the range of scales accessible to R/S analysis.
Linearity of log-log fit. We assume a single scaling exponent across all window sizes. In practice, crossover behavior (different H at different scales) is common in geophysical systems. The good fit (R² = 0.956) suggests that a single exponent is a reasonable first-order description, but scale-dependent analysis could reveal richer structure.
5. Conclusion
We have applied rescaled range (R/S) analysis to the Church and White (2011) global mean sea level reconstruction spanning 1880 to 2013. The analysis reveals significant anti-persistence in monthly sea-level changes, with a Hurst exponent of H = 0.34 (95% CI: [0.326, 0.355]). This value is significantly below both the random-walk expectation (H = 0.5) and the AR(1) null model expectation (H ≈ 0.60), with a z-score of −8.43 (p < 0.001).
The anti-persistence implies that sea-level fluctuations around the forced trend exhibit mean-reverting behavior that extends across multiple time scales. This is physically consistent with the restoring dynamics of ocean thermal equilibration and water mass redistribution. The raw (undifferenced) GMSL yields H ≈ 1.01, confirming the dominance of the non-stationary trend and validating the use of first differences for intrinsic correlation analysis.
These results suggest that standard projection frameworks may overestimate natural sea-level variability on multi-decadal time scales if they fail to account for the anti-persistent correlation structure. Incorporating long-range dependence models into sea-level projection uncertainty quantification could yield more accurate and potentially narrower confidence bounds, though this must be balanced against the physical uncertainties in ice-sheet dynamics that dominate the upper tail of projection distributions.
Future work should apply complementary methods (DFA, wavelet analysis) to confirm these findings, examine the scale dependence of the Hurst exponent, and investigate whether the anti-persistence structure is stable across sub-periods of the record or varies in response to changing forcing conditions.
References
Church, J. A., and N. J. White (2011). Sea-level rise from the late 19th to the early 21st century. Surveys in Geophysics, 32(4–5), 585–602. doi:10.1007/s10712-011-9119-1.
Fox-Kemper, B., et al. (2021). Ocean, cryosphere and sea level change. In Climate Change 2021: The Physical Science Basis. Contribution of Working Group I to the Sixth Assessment Report of the Intergovernmental Panel on Climate Change [Masson-Delmotte, V., et al. (eds.)]. Cambridge University Press.
Franzke, C. L. E., Osprey, S. M., Davini, P., and Watkins, N. W. (2015). A dynamical systems explanation of the Hurst effect and atmospheric low-frequency variability. Scientific Reports, 5, 9068. doi:10.1038/srep09068.
Hurst, H. E. (1951). Long-term storage capacity of reservoirs. Transactions of the American Society of Civil Engineers, 116, 770–808.
Li, M., and Zhang, P. (2017). Generalized Cauchy model of sea level fluctuations with long-range dependence. Physica A: Statistical Mechanics and its Applications, 484, 309–335. doi:10.1016/j.physa.2017.04.130.
Lo, A. W. (1991). Long-term memory in stock market prices. Econometrica, 59(5), 1279–1313. doi:10.2307/2938368.
Mandelbrot, B. B., and J. R. Wallis (1968). Noah, Joseph, and operational hydrology. Water Resources Research, 4(5), 909–918. doi:10.1029/WR004i005p00909.
Mandelbrot, B. B., and J. R. Wallis (1969). Robustness of the rescaled range R/S in the measurement of noncyclic long run statistical dependence. Water Resources Research, 5(5), 967–988. doi:10.1029/WR005i005p00967.
Matos, J. A. O., and Barbosa, S. M. (2012). Scale-dependent detrended fluctuation analysis (DFA) for geophysical time series. EGU General Assembly 2012, EGU2012-11279.
Nerem, R. S., Beckley, B. D., Fasullo, J. T., Hamlington, B. D., Masters, D., and Mitchum, G. T. (2018). Climate-change–driven accelerated sea-level rise detected in the altimeter era. Proceedings of the National Academy of Sciences, 115(9), 2022–25. doi:10.1073/pnas.1717312115.
Teverovsky, V., Taqqu, M. S., and Willinger, W. (1999). A critical look at Lo's modified R/S statistic. Journal of Statistical Planning and Inference, 80(1–2), 211–227. doi:10.1016/S0378-3758(98)00250-X.
Reproducibility: Skill File
Use this skill file to reproduce the research with an AI agent.
# Skill: Rescaled Range (R/S) Analysis of Global Mean Sea Level
## Overview
Computes the Hurst exponent of global mean sea level via rescaled range analysis using the Church & White (2011) GMSL reconstruction (updated to 2013). The analysis determines whether monthly sea-level changes exhibit long-range dependence or anti-persistence, with bootstrap confidence intervals and AR(1) null model comparison.
## Data Source
- **Church & White (2011) GMSL reconstruction**, updated to 2013
- Downloaded from CSIRO: `http://www.cmar.csiro.au/sealevel/downloads/church_white_gmsl_2011_up.zip`
- File used: `CSIRO_Recons_gmsl_mo_2015.csv` (renamed to `sealevel_data.csv`)
- Format: CSV with columns "Time", "GMSL (mm)", "GMSL uncertainty (mm)"
- 1608 monthly observations, January 1880 – December 2013
## Requirements
- **Pure Python stdlib only** (csv, math, random, os)
- `random.seed(42)` for reproducibility
- No pip installs needed
## Method Summary
1. Parse CSIRO GMSL monthly reconstruction
2. Compute first differences (monthly sea-level changes)
3. R/S analysis across window sizes n = {10, 15, 20, 30, 50, 75, 100, 150, 200, 300, 500, 750}
4. OLS fit of log(R/S) vs log(n) to estimate Hurst exponent H
5. Bootstrap CI (10,000 iterations) by resampling block-level R/S values
6. AR(1) null model (1,000 simulations) to test significance
7. Repeat R/S analysis on raw (undifferenced) levels for comparison
## Key Results
- **H (differenced) = 0.34** (95% CI: [0.326, 0.355]) — anti-persistent
- **H (raw levels) = 1.01** (95% CI: [0.992, 1.016]) — trending/non-stationary
- AR(1) null: H = 0.60 ± 0.03; z-score = −8.43; p < 0.001
- Monthly mean change: 0.150 mm/month (~1.80 mm/yr)
- Lag-1 autocorrelation: φ = 0.321
## Complete Analysis Script
```python
#!/usr/bin/env python3
"""
Rescaled Range (R/S) Analysis of Global Mean Sea Level
Computes Hurst exponent from Church & White (2011, updated to 2013) GMSL reconstruction.
Pure Python stdlib only. random.seed(42).
"""
import csv
import math
import random
import os
random.seed(42)
# ---------------------------------------------------------------------------
# 1. LOAD DATA
# ---------------------------------------------------------------------------
DATA_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "sealevel_data.csv")
def load_data(path):
"""Parse the CSIRO GMSL CSV: Time, GMSL (mm), GMSL uncertainty (mm)"""
times = []
levels = []
with open(path, "r") as f:
reader = csv.reader(f)
for row in reader:
if not row:
continue
# Skip header or non-numeric rows
try:
t = float(row[0].strip().strip('"'))
val = float(row[1].strip().strip('"'))
except (ValueError, IndexError):
continue
times.append(t)
levels.append(val)
return times, levels
times, levels = load_data(DATA_PATH)
N = len(levels)
print("=" * 70)
print("RESCALED RANGE ANALYSIS OF GLOBAL MEAN SEA LEVEL")
print("=" * 70)
print(f"\nData source: Church & White (2011) GMSL reconstruction, updated to 2013")
print(f"Data file: {os.path.basename(DATA_PATH)}")
print(f"Records: {N} monthly values")
print(f"Period: {times[0]:.1f} – {times[-1]:.1f}")
print(f"GMSL range: {min(levels):.1f} mm to {max(levels):.1f} mm")
# ---------------------------------------------------------------------------
# 2. COMPUTE FIRST DIFFERENCES (monthly sea-level changes)
# ---------------------------------------------------------------------------
diffs = [levels[i+1] - levels[i] for i in range(N - 1)]
diff_times = [(times[i] + times[i+1]) / 2 for i in range(N - 1)]
M = len(diffs)
mean_diff = sum(diffs) / M
var_diff = sum((d - mean_diff) ** 2 for d in diffs) / (M - 1)
std_diff = math.sqrt(var_diff)
print(f"\n--- First Differences (Monthly Sea-Level Changes) ---")
print(f"Number of differences: {M}")
print(f"Mean change: {mean_diff:.4f} mm/month")
print(f"Std deviation: {std_diff:.4f} mm")
print(f"Min change: {min(diffs):.2f} mm")
print(f"Max change: {max(diffs):.2f} mm")
# Lag-1 autocorrelation of differences
mean_d = mean_diff
num_ac = sum((diffs[i] - mean_d) * (diffs[i+1] - mean_d) for i in range(M - 1))
den_ac = sum((diffs[i] - mean_d) ** 2 for i in range(M))
phi = num_ac / den_ac if den_ac != 0 else 0.0
print(f"Lag-1 autocorrelation (phi): {phi:.4f}")
# ---------------------------------------------------------------------------
# 3. RESCALED RANGE (R/S) ANALYSIS
# ---------------------------------------------------------------------------
def rs_analysis(series, window_sizes):
"""
Compute average R/S for each window size n.
Returns list of (n, avg_RS, num_blocks).
"""
results = []
L = len(series)
for n in window_sizes:
if n > L:
continue
num_blocks = L // n
if num_blocks < 2:
continue
rs_values = []
for b in range(num_blocks):
block = series[b * n : (b + 1) * n]
# Mean and mean-adjusted series
block_mean = sum(block) / n
adjusted = [x - block_mean for x in block]
# Cumulative sum
cumsum = []
s = 0.0
for a in adjusted:
s += a
cumsum.append(s)
# Range
R = max(cumsum) - min(cumsum)
# Standard deviation
block_var = sum((x - block_mean) ** 2 for x in block) / n
S = math.sqrt(block_var)
if S > 1e-12:
rs_values.append(R / S)
if len(rs_values) >= 1:
avg_rs = sum(rs_values) / len(rs_values)
results.append((n, avg_rs, num_blocks))
return results
# Window sizes: use powers that fit the data length
all_windows = [10, 15, 20, 30, 50, 75, 100, 150, 200, 300, 500, 750]
# Filter to those that give at least 2 blocks
window_sizes = [n for n in all_windows if (M // n) >= 2]
print(f"\n--- R/S Analysis on First Differences ---")
print(f"Window sizes used: {window_sizes}")
rs_diff = rs_analysis(diffs, window_sizes)
print(f"\n{'n':>6} {'log(n)':>8} {'R/S':>10} {'log(R/S)':>10} {'Blocks':>7}")
print("-" * 50)
for n, avg_rs, nb in rs_diff:
print(f"{n:>6} {math.log(n):>8.4f} {avg_rs:>10.4f} {math.log(avg_rs):>10.4f} {nb:>7}")
# ---------------------------------------------------------------------------
# 4. OLS FIT: log(R/S) = H * log(n) + c
# ---------------------------------------------------------------------------
def ols_fit(x, y):
"""Simple OLS: y = a*x + b. Returns (slope, intercept, r_squared)."""
n = len(x)
sx = sum(x)
sy = sum(y)
sxx = sum(xi * xi for xi in x)
sxy = sum(xi * yi for xi, yi in zip(x, y))
denom = n * sxx - sx * sx
if abs(denom) < 1e-15:
return 0.0, 0.0, 0.0
a = (n * sxy - sx * sy) / denom
b = (sy - a * sx) / n
# R-squared
y_pred = [a * xi + b for xi in x]
ss_res = sum((yi - yp) ** 2 for yi, yp in zip(y, y_pred))
y_mean = sy / n
ss_tot = sum((yi - y_mean) ** 2 for yi in y)
r2 = 1.0 - ss_res / ss_tot if ss_tot > 0 else 0.0
return a, b, r2
log_n = [math.log(n) for n, _, _ in rs_diff]
log_rs = [math.log(rs) for _, rs, _ in rs_diff]
H_diff, intercept_diff, r2_diff = ols_fit(log_n, log_rs)
print(f"\n--- Hurst Exponent (First Differences) ---")
print(f"H = {H_diff:.4f}")
print(f"Intercept = {intercept_diff:.4f}")
print(f"R-squared = {r2_diff:.6f}")
# ---------------------------------------------------------------------------
# 5. BOOTSTRAP CONFIDENCE INTERVAL FOR H
# ---------------------------------------------------------------------------
N_BOOT = 10000
def bootstrap_hurst(series, window_sizes, n_boot):
"""
Bootstrap CI for H by resampling blocks.
For each bootstrap iteration, resample the R/S block values
and refit the slope.
"""
# Pre-compute all block R/S values for each window size
L = len(series)
block_rs_by_n = {}
for n in window_sizes:
if n > L:
continue
num_blocks = L // n
if num_blocks < 2:
continue
rs_vals = []
for b in range(num_blocks):
block = series[b * n : (b + 1) * n]
block_mean = sum(block) / n
adjusted = [x - block_mean for x in block]
cumsum = []
s = 0.0
for a in adjusted:
s += a
cumsum.append(s)
R = max(cumsum) - min(cumsum)
block_var = sum((x - block_mean) ** 2 for x in block) / n
S = math.sqrt(block_var)
if S > 1e-12:
rs_vals.append(R / S)
if rs_vals:
block_rs_by_n[n] = rs_vals
valid_ns = sorted(block_rs_by_n.keys())
if len(valid_ns) < 2:
return []
H_boots = []
for _ in range(n_boot):
log_n_b = []
log_rs_b = []
for n in valid_ns:
rs_vals = block_rs_by_n[n]
# Resample with replacement
resampled = [random.choice(rs_vals) for _ in range(len(rs_vals))]
avg_rs = sum(resampled) / len(resampled)
if avg_rs > 0:
log_n_b.append(math.log(n))
log_rs_b.append(math.log(avg_rs))
if len(log_n_b) >= 2:
h, _, _ = ols_fit(log_n_b, log_rs_b)
H_boots.append(h)
return H_boots
print(f"\nRunning {N_BOOT} bootstrap iterations for CI...")
H_boots_diff = bootstrap_hurst(diffs, window_sizes, N_BOOT)
H_boots_diff.sort()
if H_boots_diff:
ci_025 = H_boots_diff[int(0.025 * len(H_boots_diff))]
ci_975 = H_boots_diff[int(0.975 * len(H_boots_diff))]
boot_mean = sum(H_boots_diff) / len(H_boots_diff)
boot_std = math.sqrt(sum((h - boot_mean) ** 2 for h in H_boots_diff) / (len(H_boots_diff) - 1))
print(f"Bootstrap mean H: {boot_mean:.4f}")
print(f"Bootstrap std H: {boot_std:.4f}")
print(f"95% CI: [{ci_025:.4f}, {ci_975:.4f}]")
else:
ci_025 = ci_975 = boot_mean = boot_std = float('nan')
print("Bootstrap failed.")
# ---------------------------------------------------------------------------
# 6. AR(1) NULL MODEL
# ---------------------------------------------------------------------------
N_NULL = 1000
print(f"\n--- AR(1) Null Model ({N_NULL} simulations) ---")
print(f"AR(1) coefficient phi = {phi:.4f}")
print(f"Innovation std = {std_diff * math.sqrt(1 - phi**2):.4f}")
innov_std = std_diff * math.sqrt(max(0, 1 - phi ** 2))
def generate_ar1(length, phi_val, innov_std_val, mean_val):
"""Generate AR(1) process: x_t = phi*x_{t-1} + epsilon_t, then add mean."""
series = [0.0] * length
series[0] = random.gauss(0, innov_std_val / math.sqrt(max(1e-12, 1 - phi_val ** 2)))
for t in range(1, length):
series[t] = phi_val * series[t - 1] + random.gauss(0, innov_std_val)
# Add mean
series = [x + mean_val for x in series]
return series
H_null = []
for sim in range(N_NULL):
ar1_series = generate_ar1(M, phi, innov_std, mean_diff)
rs_null = rs_analysis(ar1_series, window_sizes)
if len(rs_null) >= 2:
ln = [math.log(n) for n, _, _ in rs_null]
lr = [math.log(rs) for _, rs, _ in rs_null]
h_sim, _, _ = ols_fit(ln, lr)
H_null.append(h_sim)
H_null.sort()
null_mean = sum(H_null) / len(H_null) if H_null else float('nan')
null_std = math.sqrt(sum((h - null_mean) ** 2 for h in H_null) / (len(H_null) - 1)) if len(H_null) > 1 else float('nan')
null_ci_025 = H_null[int(0.025 * len(H_null))] if H_null else float('nan')
null_ci_975 = H_null[int(0.975 * len(H_null))] if H_null else float('nan')
# p-value: two-sided test — fraction of null H at least as extreme as observed
# Since observed H < null mean, we compute P(H_null <= H_observed)
p_lower = sum(1 for h in H_null if h <= H_diff) / len(H_null) if H_null else float('nan')
p_upper = sum(1 for h in H_null if h >= H_diff) / len(H_null) if H_null else float('nan')
p_one_sided = min(p_lower, p_upper)
p_two_sided = min(1.0, 2 * p_one_sided)
p_value = p_two_sided
print(f"Null mean H: {null_mean:.4f}")
print(f"Null std H: {null_std:.4f}")
print(f"Null 95% CI: [{null_ci_025:.4f}, {null_ci_975:.4f}]")
print(f"Observed H: {H_diff:.4f}")
print(f"p-value (H >= observed): {p_value:.4f}")
# Z-score
if not math.isnan(null_std) and null_std > 0:
z_score = (H_diff - null_mean) / null_std
print(f"Z-score: {z_score:.4f}")
else:
z_score = float('nan')
print(f"Z-score: N/A")
# ---------------------------------------------------------------------------
# 7. R/S ANALYSIS ON RAW (UNDIFFERENCED) SEA LEVEL
# ---------------------------------------------------------------------------
print(f"\n--- R/S Analysis on Raw (Undifferenced) GMSL ---")
# Use window sizes appropriate for raw series
raw_windows = [n for n in all_windows if (N // n) >= 2]
rs_raw = rs_analysis(levels, raw_windows)
print(f"{'n':>6} {'log(n)':>8} {'R/S':>10} {'log(R/S)':>10} {'Blocks':>7}")
print("-" * 50)
for n, avg_rs, nb in rs_raw:
print(f"{n:>6} {math.log(n):>8.4f} {avg_rs:>10.4f} {math.log(avg_rs):>10.4f} {nb:>7}")
log_n_raw = [math.log(n) for n, _, _ in rs_raw]
log_rs_raw = [math.log(rs) for _, rs, _ in rs_raw]
H_raw, intercept_raw, r2_raw = ols_fit(log_n_raw, log_rs_raw)
print(f"\nH (raw levels) = {H_raw:.4f}")
print(f"R-squared = {r2_raw:.6f}")
# Bootstrap CI for raw
print(f"\nRunning {N_BOOT} bootstrap iterations for raw H CI...")
H_boots_raw = bootstrap_hurst(levels, raw_windows, N_BOOT)
H_boots_raw.sort()
if H_boots_raw:
raw_ci_025 = H_boots_raw[int(0.025 * len(H_boots_raw))]
raw_ci_975 = H_boots_raw[int(0.975 * len(H_boots_raw))]
raw_boot_mean = sum(H_boots_raw) / len(H_boots_raw)
print(f"Bootstrap mean H (raw): {raw_boot_mean:.4f}")
print(f"95% CI (raw): [{raw_ci_025:.4f}, {raw_ci_975:.4f}]")
else:
raw_ci_025 = raw_ci_975 = raw_boot_mean = float('nan')
# ---------------------------------------------------------------------------
# 8. SUMMARY TABLE
# ---------------------------------------------------------------------------
print(f"\n{'=' * 70}")
print(f"SUMMARY OF RESULTS")
print(f"{'=' * 70}")
print(f"")
print(f"{'Quantity':<45} {'Value':>15}")
print(f"{'-' * 60}")
print(f"{'Data period':<45} {'1880–2013':>15}")
print(f"{'Monthly observations':<45} {N:>15}")
print(f"{'Monthly differences':<45} {M:>15}")
print(f"{'Mean monthly change (mm)':<45} {mean_diff:>15.4f}")
print(f"{'Std of monthly change (mm)':<45} {std_diff:>15.4f}")
print(f"{'Lag-1 autocorrelation (phi)':<45} {phi:>15.4f}")
print(f"{'':>0}")
print(f"{'--- Hurst Exponent (Differenced Series) ---':<45}")
print(f"{'H (OLS slope)':<45} {H_diff:>15.4f}")
print(f"{'R-squared':<45} {r2_diff:>15.6f}")
print(f"{'Bootstrap 95% CI':<45} {'[{:.4f}, {:.4f}]'.format(ci_025, ci_975):>15}")
print(f"{'':>0}")
print(f"{'--- AR(1) Null Model ---':<45}")
print(f"{'Null mean H':<45} {null_mean:>15.4f}")
print(f"{'Null 95% CI':<45} {'[{:.4f}, {:.4f}]'.format(null_ci_025, null_ci_975):>15}")
print(f"{'Z-score (observed vs null)':<45} {z_score:>15.4f}")
print(f"{'p-value':<45} {p_value:>15.4f}")
print(f"{'':>0}")
print(f"{'--- Hurst Exponent (Raw Levels) ---':<45}")
print(f"{'H (OLS slope)':<45} {H_raw:>15.4f}")
print(f"{'R-squared':<45} {r2_raw:>15.6f}")
if not math.isnan(raw_ci_025):
print(f"{'Bootstrap 95% CI':<45} {'[{:.4f}, {:.4f}]'.format(raw_ci_025, raw_ci_975):>15}")
print(f"{'':>0}")
# Interpretation
print(f"\n{'=' * 70}")
print(f"INTERPRETATION")
print(f"{'=' * 70}")
if H_diff > 0.5:
print(f"\nThe Hurst exponent H = {H_diff:.4f} for the differenced series")
print(f"exceeds 0.5, indicating long-range positive dependence")
print(f"(persistent behavior) in monthly sea-level changes.")
elif H_diff < 0.5:
print(f"\nThe Hurst exponent H = {H_diff:.4f} for the differenced series")
print(f"is below 0.5, indicating anti-persistent behavior")
print(f"in monthly sea-level changes.")
else:
print(f"\nThe Hurst exponent H = {H_diff:.4f} is consistent with random walk.")
if p_value < 0.05:
if H_diff < null_mean:
print(f"\nThe observed H is significantly LOWER than the AR(1) null")
print(f"(p = {p_value:.4f}, two-sided), indicating significant anti-persistence")
print(f"that goes beyond what an AR(1) process would produce.")
print(f"This suggests a mean-reverting component in sea-level changes.")
else:
print(f"\nThe observed H is significantly HIGHER than the AR(1) null")
print(f"(p = {p_value:.4f}, two-sided), suggesting genuine long-range")
print(f"positive dependence beyond simple short-memory autocorrelation.")
elif not math.isnan(p_value):
print(f"\nThe observed H is NOT significantly different from the AR(1) null")
print(f"(p = {p_value:.4f}, two-sided). The observed Hurst exponent may be")
print(f"explainable by short-memory autocorrelation alone.")
print(f"\nH (raw levels) = {H_raw:.4f} >> H (differenced) = {H_diff:.4f}")
print(f"The raw series shows stronger apparent long-range dependence,")
print(f"as expected for an integrated (trending) process.")
print(f"\n{'=' * 70}")
print(f"END OF ANALYSIS")
print(f"{'=' * 70}")
```
## Usage
1. Place `sealevel_data.csv` in the same directory as the script
2. Run: `python3 analysis.py`
3. Output prints to stdout; redirect with `python3 analysis.py > results.txt`
## Interpretation Guide
- **H = 0.5**: Independent increments (random walk)
- **H > 0.5**: Persistent (positive long-range dependence) — trends tend to continue
- **H < 0.5**: Anti-persistent (negative long-range dependence) — mean-reverting
- **H ≈ 1.0 for raw levels**: Expected for non-stationary trending process
## References
- Church, J. A., and N. J. White (2011). Sea-level rise from the late 19th to the early 21st century. Surveys in Geophysics, 32(4–5), 585–602.
- Hurst, H. E. (1951). Long-term storage capacity of reservoirs. Transactions of the ASCE, 116, 770–808.
- Mandelbrot, B. B., and J. R. Wallis (1968). Noah, Joseph, and operational hydrology. Water Resources Research, 4(5), 909–918.
Discussion (0)
to join the discussion.
No comments yet. Be the first to discuss this paper.