Cross-Correlation

Cross-Correlation#

Analyze the time-lagged relationship between two signals using cross-correlation.

Goal

Analyze the time-lagged correlation between outdoor temperature and indoor temperature (Flat A). Cross-correlation reveals how strongly and with what delay the outdoor climate influences the indoor environment.

Data Basis

Datasets:

  • flatTempHum.csv — Hourly indoor temperature (FlatA_Temp)

  • centralOutsideTemp.csv — Hourly outdoor temperature (centralOutsideTemp)

Time range: Overlapping period of both datasets
Lag range: -48 to +48 hours

Data Preparation#

import pandas as pd

base_url = "https://raw.githubusercontent.com/retomarek/edap/main/edap/sampleData/"

# Load indoor temperature
df_indoor = pd.read_csv(base_url + "flatTempHum.csv", sep=";", usecols=["time", "FlatA_Temp"])
df_indoor["time"] = pd.to_datetime(df_indoor["time"])
df_indoor = df_indoor.set_index("time", drop=True)

# Load outdoor temperature
df_outdoor = pd.read_csv(base_url + "centralOutsideTemp.csv", sep=";")
df_outdoor["time"] = pd.to_datetime(df_outdoor["time"])
df_outdoor = df_outdoor.set_index("time", drop=True)

df_indoor.head()
FlatA_Temp
time
2018-10-03 00:00:00 24.43
2018-10-03 01:00:00 24.40
2018-10-03 02:00:00 24.40
2018-10-03 03:00:00 24.40
2018-10-03 04:00:00 24.40
# Join on the time index and keep a regular hourly grid: missing hours stay NaN,
# so that .shift(1) below really moves the data by one hour
df = df_indoor.join(df_outdoor, how="inner").asfreq("h")
df = df.rename(columns={"centralOutsideTemp": "outdoor", "FlatA_Temp": "indoor"})
print(f"Combined dataset: {len(df)} hours, {df['indoor'].isna().sum()} without indoor value")
df.head()
Combined dataset: 17172 hours, 688 without indoor value
indoor outdoor
time
2018-10-03 00:00:00 24.43 11.80
2018-10-03 01:00:00 24.40 11.25
2018-10-03 02:00:00 24.40 11.45
2018-10-03 03:00:00 24.40 11.40
2018-10-03 04:00:00 24.40 11.10

Visualization#

Both temperatures follow the annual cycle (warm in summer, cold in winter), so their raw values correlate strongly at every lag. To see the delay of the building, we correlate the hour-to-hour changes (.diff()) instead. indoor.corr(outdoor.shift(lag)) compares the indoor change with the outdoor change lag hours earlier: a peak at a positive lag means the indoor temperature follows the outdoor temperature with that delay.

import plotly.express as px

changes = df.diff()   # hour-to-hour changes, NaN next to missing hours

max_lag = 48
lags = range(-max_lag, max_lag + 1)
correlations = [changes["indoor"].corr(changes["outdoor"].shift(lag)) for lag in lags]
df_corr = pd.DataFrame({"lag_hours": list(lags), "correlation": correlations})

best = df_corr.loc[df_corr["correlation"].idxmax()]
print(f"Maximum correlation: {best['correlation']:.2f} at lag = {int(best['lag_hours'])} hours")
Maximum correlation: 0.22 at lag = 4 hours
fig = px.bar(
    df_corr,
    x="lag_hours",
    y="correlation",
    title="Hourly Changes: Outdoor vs. Indoor Temperature (Flat A)",
    labels={"lag_hours": "Lag [hours]", "correlation": "Pearson Correlation"},
)
fig.update_layout(bargap=0)
fig.show()

The indoor temperature follows the outdoor temperature with a delay of a few hours (thermal inertia of the building). The correlation is weak because heating and occupants change the indoor temperature much more than the weather does from one hour to the next.

Customization
  • Adjust max_lag to explore longer or shorter delay windows.

  • Correlate the raw values (df instead of changes) to see how the shared annual cycle hides the delay: the correlation is high at every lag.

  • Replace px.bar with px.line for a smoother correlation curve.