
В данном проекте анализируется временной ряд концентрации углекислого газа (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₂ по мере увеличения её абсолютного уровня. Использование робастной трендовой линии подчёркивает аналитический характер графика и демонстрирует применение статистических методов за пределами базовой визуализации.
В совокупности все графики образуют логически связанную систему: от общего тренда — к локальной вариативности, затем к сезонным структурам и, наконец, к исследованию динамики роста. Такое соотношение визуализаций с целью исследования позволяет не просто представить данные, но и последовательно объяснить их поведение, что делает проект одновременно аналитическим и обучающим.