В данном проекте анализируется временной ряд концентрации углекислого газа (CO₂) в атмосфере на станции Mauna Loa за период 1958–2001 годов.
Эти данные являются одними из самых известных и надёжных измерений атмосферного CO₂ и широко используются для изучения долгосрочных климатических изменений.
Цель проекта — с помощью инструментов анализа данных в Python (Pandas) исследовать динамику изменения концентрации CO₂, выявить долгосрочный тренд роста и сезонные колебания, а также наглядно представить полученные результаты в виде стилизованных и объясняющих визуализаций. Особое внимание уделяется не только построению графиков, но и этапам обработки данных, выбору статистических методов и интерпретации полученных выводов.
В рамках данного проекта были использованы данные о концентрации углекислого газа (CO₂) в атмосфере, измеряемые на обсерватории Mauna Loa (Гавайи) в период с 1958 по 2001 год. Датасет представляет собой временной ряд с еженедельными измерениями и был получен из открытого набора данных co2, встроенного в библиотеку statsmodels, которая агрегирует и распространяет данные на основе реальных научных измерений.
Выбор именно этих данных обусловлен их высокой научной и исторической ценностью. Ряд Mauna Loa считается эталонным источником для анализа долгосрочных изменений концентрации CO₂ в атмосфере и часто используется в климатических исследованиях как доказательство устойчивого роста парниковых газов. Кроме того, данные хорошо подходят для учебного анализа: в них присутствуют пропуски, выраженная сезонность и долгосрочный тренд, что позволяет продемонстрировать полноценный процесс обработки, анализа и интерпретации временных рядов.
Для визуализации данных были выбраны несколько типов графиков, каждый из которых решает свою аналитическую задачу. Линейные графики используются для отображения динамики концентрации CO₂ во времени и выявления долгосрочного тренда. Гистограммы применяются для анализа распределения месячных изменений концентрации CO₂ и оценки их вариативности. Также используется тепловая карта (heatmap), позволяющая наглядно показать сезонные колебания по годам, и диаграмма рассеяния для изучения связи между текущим уровнем CO₂ и годовыми изменениями. Такой набор графиков позволяет рассмотреть данные с разных сторон и представить результаты анализа в наглядном и объясняющем формате.
Начальный код
import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib.dates as mdates import matplotlib as mpl import statsmodels.api as sm
df = sm.datasets.co2.load_pandas().data.copy() df.index.name = "date" df = df.rename(columns={"co2":"co2_ppm"}) df["co2_ppm"] = df["co2_ppm"].astype(float)
df.head(), df.shape
df["co2_ppm_interp"] = df["co2_ppm"].interpolate(method="time")
monthly = df["co2_ppm_interp"].resample("MS").mean().to_frame("co2_ppm_monthly") monthly["co2_ppm_12m_roll"] = monthly["co2_ppm_monthly"].rolling(12, min_periods=6).mean() monthly["mom_change"] = monthly["co2_ppm_monthly"].diff() monthly["yoy_change"] = monthly["co2_ppm_monthly"].diff(12)
monthly["year"] = monthly.index.year monthly["month"] = monthly.index.month monthly["month_name"] = monthly.index.strftime("%b")
monthly.head(), monthly.shape
Создание стиля графиков
Для создания стиля графиков я улучшала код с помощью Chat GPT
Промпт: Сделай несколько графиков в Python (Pandas + Matplotlib) в едином, аккуратном стиле.
Хочу тёмную тему, ограниченную цветовую палитру с 1–2 акцентными цветами, без стандартного оформления Matplotlib. Графики должны выглядеть как инфографика, а не как технические черновики.
Нужны разные типы графиков: линейный график по времени, гистограмма, heatmap и scatter. Важно, чтобы графики не просто показывали данные, а помогали их понять — выделяли тренды, сезонность и разброс.
Все цвета, шрифты и оформление задай прямо в коде.
PALETTE = { "bg": "#0B1220", "panel": "#0F1A2B", "grid": "#24324A", "text": "#E9EEF7", "muted": "#A8B3C7", "accent": "#7C5CFF", "accent2": "#2ED3B7", "warn": "#FFB020", "danger": "#FF5C7A", }
mpl.rcParams.update({ "figure.facecolor": PALETTE["bg"], "axes.facecolor": PALETTE["panel"], "savefig.facecolor": PALETTE["bg"], "axes.edgecolor": PALETTE["grid"], "axes.labelcolor": PALETTE["text"], "xtick.color": PALETTE["muted"], "ytick.color": PALETTE["muted"], "text.color": PALETTE["text"], "grid.color": PALETTE["grid"], "grid.linestyle": "-", "grid.alpha": 0.55, "axes.grid": True, "axes.titleweight": "bold", "axes.titlesize": 16, "axes.labelsize": 12, "font.size": 12, "legend.frameon": False, })
def finish(ax, title, subtitle=None, xlabel=None, ylabel=None): ax.set_title(title, loc="left", pad=12) if subtitle: ax.text(0, 1.02, subtitle, transform=ax.transAxes, ha="left", va="bottom", color=PALETTE["muted"], fontsize=11) if xlabel: ax.set_xlabel(xlabel, labelpad=10) if ylabel: ax.set_ylabel(ylabel, labelpad=10) for spine in ax.spines.values(): spine.set_alpha(0.6) ax.grid(True, which="major") ax.tick_params(axis='both', which='major', length=0) return ax
График 1 тренд + сезонность
fig, ax = plt.subplots(figsize=(12,6), dpi=160) ax.plot(monthly.index, monthly["co2_ppm_monthly"], lw=1.4, color=PALETTE["accent"], alpha=0.9, label="Monthly mean (ppm)") ax.plot(monthly.index, monthly["co2_ppm_12m_roll"], lw=2.2, color=PALETTE["accent2"], alpha=0.95, label="12‑month rolling mean") finish(ax, "CO₂ at Mauna Loa: growth + seasonality (1958–2001)", "Monthly average concentration; missing weekly values interpolated before monthly aggregation.", ylabel="CO₂ (ppm)") ax.xaxis.set_major_locator(mdates.YearLocator(5)) ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y')) ax.legend(loc="upper left", fontsize=10) plt.show()
График 2 гистограмма месяц к месяцу
chg = monthly["mom_change"].dropna() fig, ax = plt.subplots(figsize=(10,6), dpi=160) ax.hist(chg, bins=30, color=PALETTE["warn"], alpha=0.85, edgecolor=PALETTE["panel"]) finish(ax, "How volatile is CO₂ month‑to‑month?", "Distribution of ΔCO₂ (ppm) between consecutive months.", xlabel="ΔCO₂ (ppm, month over month)", ylabel="Number of months") ax.axvline(chg.mean(), color=PALETTE["accent2"], lw=2, label=f"Mean = {chg.mean():.2f} ppm") ax.axvline(chg.median(), color=PALETTE["accent"], lw=2, label=f"Median = {chg.median():.2f} ppm") ax.legend(loc="upper right", fontsize=10) plt.show()
График 3 сезонный профиль + IQR
by_month = monthly.groupby("month")["co2_ppm_monthly"] m_mean = by_month.mean() m_q25 = by_month.quantile(0.25) m_q75 = by_month.quantile(0.75)
fig, ax = plt.subplots(figsize=(10,6), dpi=160) x = np.arange(1,13) ax.fill_between(x, m_q25.values, m_q75.values, color=PALETTE["grid"], alpha=0.8, label="IQR (25–75%)") ax.plot(x, m_mean.values, color=PALETTE["accent2"], lw=3, marker="o", ms=5, label="Mean by month") ax.set_xticks(x) ax.set_xticklabels([pd.Timestamp(2000, m, 1).strftime("%b") for m in x]) finish(ax, "Seasonality: a typical year at Mauna Loa", "Average monthly cycle across all years; shaded band shows interquartile range.", xlabel="Month", ylabel="CO₂ (ppm)") ax.legend(loc="upper left", fontsize=10) plt.show()
График 4 heatmap
pivot = monthly.pivot_table(index="year", columns="month", values="co2_ppm_monthly", aggfunc="mean") z = (pivot.sub(pivot.mean(axis=1), axis=0)).div(pivot.std(axis=1), axis=0)
fig, ax = plt.subplots(figsize=(12,7), dpi=160) im = ax.imshow(z.values, aspect="auto", interpolation="nearest") ax.set_yticks(np.arange(len(pivot.index))) ax.set_yticklabels(pivot.index.astype(int)) ax.set_xticks(np.arange(12)) ax.set_xticklabels([pd.Timestamp(2000, m, 1).strftime("%b") for m in range(1,13)]) finish(ax, "Seasonal pattern by year (shape, not level)", "Each row is one year; values are z‑scores within the year to emphasize the seasonal swing.", xlabel="Month", ylabel="Year") cbar = fig.colorbar(im, ax=ax, fraction=0.035, pad=0.02) cbar.set_label("Within‑year z‑score", color=PALETTE["text"]) plt.show()
График 5 изменение year over year
from sklearn.linear_model import TheilSenRegressor
yoy = monthly.dropna(subset=["yoy_change"]) fig, ax = plt.subplots(figsize=(10,6), dpi=160) ax.scatter(yoy["co2_ppm_monthly"], yoy["yoy_change"], s=18, alpha=0.55, color=PALETTE["danger"]) finish(ax, "Is the annual growth speeding up?", "Each dot is a month; y = ΔCO₂ vs same month a year earlier.", xlabel="CO₂ level (ppm, monthly)", ylabel="YoY change (ppm)")
X = yoy["co2_ppm_monthly"].values.reshape(-1,1) y = yoy["yoy_change"].values ts = TheilSenRegressor(random_state=0).fit(X, y) xx = np.linspace(X.min(), X.max(), 200).reshape(-1,1) ax.plot(xx.ravel(), ts.predict(xx), lw=2.5, color=PALETTE["accent2"], label="Theil–Sen trend") ax.legend(loc="upper left", fontsize=10) plt.show()
Вывод
В ходе проекта был проведён анализ временного ряда концентрации углекислого газа (CO₂) на станции Mauna Loa с целью выявления долгосрочной динамики, сезонных закономерностей и характера изменений во времени. Поставленная цель — понять структуру данных и наглядно объяснить происходящие процессы с помощью визуализации — была достигнута за счёт использования нескольких взаимодополняющих типов графиков.
Линейный график временного ряда с добавлением 12-месячного скользящего среднего является ключевым элементом исследования. Он напрямую соотносится с основной целью проекта — показать устойчивый рост концентрации CO₂ во времени и одновременно отделить долгосрочный тренд от сезонных колебаний. Без этого графика было бы невозможно корректно интерпретировать общую динамику данных.
Гистограмма месячных изменений (month-over-month) дополняет временной анализ и позволяет оценить характер краткосрочной вариативности. Этот график показывает, что изменения концентрации CO₂ от месяца к месяцу в среднем невелики и распределены вокруг нуля, а наблюдаемая изменчивость в значительной степени связана с сезонным циклом. Таким образом, гистограмма отвечает на вопрос о стабильности и «шумности» временного ряда.
График сезонного профиля по месяцам с отображением межквартильного размаха (IQR) напрямую служит задаче выявления сезонности. Он демонстрирует повторяющийся годовой цикл концентрации CO₂ и показывает, что форма этого цикла остаётся устойчивой на протяжении десятилетий, а разброс значений по месяцам относительно невелик. Этот график переводит абстрактное понятие сезонности в наглядную и легко интерпретируемую форму.
Тепловая карта (heatmap) «год × месяц» расширяет анализ сезонности, позволяя сравнить её структуру между разными годами. За счёт нормализации внутри каждого года график фокусируется не на абсолютных значениях, а на форме сезонного колебания, что помогает увидеть устойчивость паттернов и возможные отклонения. Этот график связывает долгосрочный тренд и сезонность в единую визуальную структуру.
Дополнительная диаграмма рассеяния, показывающая связь между текущим уровнем CO₂ и годовыми изменениями (year-over-year), служит исследовательским элементом проекта. Она позволяет проверить гипотезу о том, меняется ли скорость роста концентрации CO₂ по мере увеличения её абсолютного уровня. Использование робастной трендовой линии подчёркивает аналитический характер графика и демонстрирует применение статистических методов за пределами базовой визуализации.
В совокупности все графики образуют логически связанную систему: от общего тренда — к локальной вариативности, затем к сезонным структурам и, наконец, к исследованию динамики роста. Такое соотношение визуализаций с целью исследования позволяет не просто представить данные, но и последовательно объяснить их поведение, что делает проект одновременно аналитическим и обучающим.




