Original size 1140x1600

Атмосферный CO₂ на Mauna Loa

PROTECT STATUS: not protected

В данном проекте анализируется временной ряд концентрации углекислого газа (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 тренд + сезонность

Original size 2368x1168

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 гистограмма месяц к месяцу

Original size 1968x1168

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

Original size 1968x1168

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

Original size 2365x1368

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

Original size 1968x1168

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₂ по мере увеличения её абсолютного уровня. Использование робастной трендовой линии подчёркивает аналитический характер графика и демонстрирует применение статистических методов за пределами базовой визуализации.

В совокупности все графики образуют логически связанную систему: от общего тренда — к локальной вариативности, затем к сезонным структурам и, наконец, к исследованию динамики роста. Такое соотношение визуализаций с целью исследования позволяет не просто представить данные, но и последовательно объяснить их поведение, что делает проект одновременно аналитическим и обучающим.

Атмосферный CO₂ на Mauna Loa
Project created at 16.01.2026
We use cookies to improve the operation of the website and to enhance its usability. More detailed information on the use of cookies can be fo...
Show more