Аналитика данных стала неотъемлемой частью современного бизнеса и научных исследований. И одним из ключевых аспектов анализа данных являются временные ряды. Эффективная работа с временными рядами играет критическую роль в прогнозировании, стратегическом планировании и принятии решений в различных отраслях.

Временные ряды — это наборы данных, где каждая точка данных связана с определенным моментом времени. Это может быть что угодно, от ежедневных финансовых показателей до ежечасных кликов на веб-сайте или даже месячных показателей погоды. Зачем нам это нужно? Потому что временные ряды предоставляют нам ценную информацию о том, как меняются данные со временем.

Области, где временные ряды играют решающую роль:

1. Финансовая аналитика: Прогнозирование цен акций, анализ рыночных тенденций и определение оптимального времени для инвестиций.

2. Маркетинг и анализ пользовательской активности: Отслеживание изменений в поведении пользователей на веб-сайте, прогнозирование спроса на товары и услуги.

3. Прогнозирование спроса: Определение оптимального уровня запасов, чтобы избежать дефицита или избытка товаров.

4. Анализ временных данных о заболеваниях: Оценка распространения эпидемий, прогнозирование заболеваемости и смертности.

5. Климатические исследования: Изучение изменений в климатических параметрах, таких как температура и осадки, для анализа климатических тенденций.

6. Прогнозирование трафика: Анализ и прогнозирование трафика на веб-сайтах и в сетях.

7. Промышленное оборудование и обслуживание: Предсказание времени отказа оборудования и оптимизация производственных процессов.

Временные ряды также присутствуют в повседневной жизни: температурные измерения, динамика финансовых индексов или даже ежедневные показатели физической активности с помощью носимых устройств — все это временные ряды.

Временные ряды могут быть стационарными (когда статистические характеристики, такие как среднее и дисперсия, остаются постоянными во времени) или нестационарными (когда эти характеристики изменяются с течением времени). Понимание природы временного ряда важно для выбора подходящих методов анализа.

Основные характеристики временных рядов


Тренд: Тренд представляет собой долгосрочное изменение в данных. Это может быть рост или спад. Например, если продажи вашей компании растут каждый месяц в течение года, это будет проявление тренда.

Сезонность: Сезонность — это циклические изменения данных, которые повторяются с постоянным интервалом времени. Например, продажи игрушек могут расти перед праздниками и падать после них.

Шум: Шум представляет собой случайные колебания данных, которые не подчиняются определенным закономерностям. Это может быть вызвано различными факторами, такими как случайные события или ошибки измерения.

Циклы: Циклы — это долгосрочные колебания данных, которые не связаны с сезонностью. Например, экономические циклы могут вызывать волны роста и спада в продажах.

Стационарность: Стационарный временной ряд — это ряд, в котором статистические характеристики, такие как среднее и дисперсия, остаются постоянными с течением времени. Многие методы анализа временных рядов предполагают стационарность данных.

Автокорреляция: Автокорреляция — это корреляция между значениями ряда в разные моменты времени. Она может помочь выявить закономерности в данных.

Пропущенные значения: Временные ряды могут содержать пропущенные значения, которые требуется обработать перед анализом.

Подготовка к анализу


Прежде чем мы начнем работу, нам нужно убедиться, что у нас есть все необходимые библиотеки для анализа временных рядов:

1. pandas: Pandas — это библиотека для работы с данными, которая предоставляет удобные структуры данных, такие как DataFrame, и множество функций для анализа и манипуляции данными. Она идеально подходит для работы с временными рядами, так как позволяет легко хранить и анализировать временные данные.

2. numpy: NumPy — это библиотека для работы с массивами и матрицами чисел. Она полезна при выполнении вычислений над данными временных рядов.

3. matplotlib и seaborn: Эти библиотеки позволяют создавать графики и визуализации данных, что особенно важно при анализе временных рядов.

4. statsmodels: Statsmodels — это библиотека для статистического анализа данных. Она содержит множество методов для анализа временных рядов, включая модели ARIMA и SARIMA.

5. scikit-learn: Scikit-learn предоставляет множество инструментов для машинного обучения, включая модели регрессии и классификации, которые можно использовать для анализа временных рядов.

Устанавливаем:

!pip install pandas numpy matplotlib seaborn statsmodels scikit-learn

Теперь, когда у нас есть необходимые библиотеки, давайте перейдем к работе с датами и временем.

Одной из ключевых характеристик временных рядов является наличие временных меток, которые позволяют нам связывать данные с конкретными моментами времени. При работе с временными рядами необходимо уметь обращаться с датами и временем.

Для начала давайте создадим небольшой dataset с данными о продажах электроники за несколько месяцев. Для примера давайте представим, что у нас есть следующие данные:

import pandas as pd

data = {'Дата': ['2023-01-01', '2023-02-01', '2023-03-01', '2023-04-01', '2023-05-01'],
        'Продажи': [1000, 1200, 1300, 1100, 1400]}

df = pd.DataFrame(data)

# Преобразуем столбец 'Дата' в формат даты
df['Дата'] = pd.to_datetime(df['Дата'])

print(df)

Результат будет следующим:

        Дата  Продажи
0 2023-01-01     1000
1 2023-02-01     1200
2 2023-03-01     1300
3 2023-04-01     1100
4 2023-05-01     1400

Теперь у нас есть DataFrame с данными о продажах и столбцом 'Дата', который имеет тип datetime64. Это позволяет нам выполнять различные операции, такие как выбор данных по дате или вычисление временных интервалов.

Пример:

# Выбор данных по диапазону дат
subset = df[(df['Дата'] >= '2023-03-01') & (df['Дата'] <= '2023-04-30')]

print(subset)

Вывод:

        Дата  Продажи
2 2023-03-01     1300
3 2023-04-01     1100

Таким образом, работа с датами и временем в pandas позволяет нам легко фильтровать и анализировать данные временных рядов.

Обработка пропущенных значений


Пропущенные значения — это обычное явление при работе с данными временных рядов. Они могут возникать по разным причинам, например, из-за ошибок при сборе данных или временных перерывов в измерениях. Поэтому важно знать, как обрабатывать пропущенные значения.

Давайте рассмотрим, как можно обработать пропущенные значения в DataFrame с помощью pandas. Для примера допустим, что у нас есть следующий dataset:

import pandas as pd

data = {'Дата': ['2023-01-01', '2023-02-01', '2023-03-01', '2023-04-01', '2023-05-01'],
        'Продажи': [1000, None, 1300, 1100, 1400]}

df = pd.DataFrame(data)

# Преобразуем столбец 'Дата' в формат даты
df['Дата'] = pd.to_datetime(df['Дата'])

print(df)

Результат:

       Дата  Продажи
0 2023-01-01   1000.0
1 2023-02-01      NaN
2 2023-03-01   1300.0
3 2023-04-01   1100.0
4 2023-05-01   1400.0

Как видите, у нас есть пропущенное значение (NaN) в столбце 'Продажи'. Существует несколько способов обработки таких значений:

1. Удаление строк с пропущенными значениями:

df.dropna(inplace=True)

Этот метод удаляет строки, содержащие пропущенные значения. Важно помнить, что это может привести к потере данных.

2. Замена пропущенных значений:

df.fillna(0, inplace=True)

Здесь мы заменяем все пропущенные значения на 0. Вы можете выбрать другое значение для замены вместо 0 в зависимости от контекста.

3. Интерполяция:

df.interpolate(inplace=True)

Интерполяция позволяет заполнить пропущенные значения на основе соседних значений. Это может быть полезно, если данные имеют некоторую структуру.

Обработка пропущенных значений зависит от конкретной задачи и данных временных рядов. Важно выбирать подходящий метод в каждом случае.

Анализ временных рядов


Мы переходим к разделу анализа временных рядов, который поможет нам раскрывать информацию и закономерности, скрытые в данных. Этот этап позволяет нам понять структуру временных рядов, определить их стационарность и выделить основные компоненты, такие как тренд, сезонность и шум.

Стационарность временных рядов


Стационарность — одно из важнейших свойств временных рядов. Стационарный ряд — это ряд, в котором статистические характеристики, такие как среднее и дисперсия, остаются постоянными во времени. Это свойство позволяет нам строить надежные модели и прогнозировать будущие значения.

1. Тесты на стационарность:

Первый шаг в анализе временных рядов — проверка стационарности. Для этого существует несколько статистических тестов. Один из них — тест Дики-Фуллера. Давайте применим его к нашему небольшому dataset с данными о продажах в условном филиале МВидео:

import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import adfuller

# Создаем dataset с данными о продажах
data = {'Дата': ['2023-01-01', '2023-02-01', '2023-03-01', '2023-04-01', '2023-05-01'],
        'Продажи': [1000, 1200, 1300, 1100, 1400]}

df = pd.DataFrame(data)

# Преобразуем столбец 'Дата' в формат даты
df['Дата'] = pd.to_datetime(df['Дата'])

# Построим график продаж
plt.plot(df['Дата'], df['Продажи'])
plt.title('Продажи в магазине МВидео')
plt.xlabel('Дата')
plt.ylabel('Продажи')
plt.show()

# Проведем тест Дики-Фуллера на стационарность
result = adfuller(df['Продажи'])

print('ADF Statistic: %f' % result[0])
print('p-value: %f' % result[1])
print('Critical Values:')
for key, value in result[4].items():
    print('\t%s: %.3f' % (key, value))



На выходе мы получим статистику теста Дики-Фуллера и p-значение. Если p-значение меньше уровня значимости (обычно 0.05), то мы можем отклонить нулевую гипотезу о нестационарности ряда и считать его стационарным.

2. Преобразование ряда для достижения стационарности:

Если начальный ряд не является стационарным, то его можно преобразовать. Например, можно вычесть тренд и сезонные компоненты, чтобы получить стационарный остаток.

# Преобразование для удаления тренда
df['Продажи_без_тренда'] = df['Продажи'] - df['Продажи'].rolling(window=2).mean()

# Преобразование для удаления сезонности (в данном случае просто разница между текущим и предыдущим значением)
df['Продажи_стационарные'] = df['Продажи_без_тренда'].diff()

# Удалим первые строки с пропущенными значениями
df.dropna(inplace=True)

# Построим графики
plt.plot(df['Дата'], df['Продажи_без_тренда'], label='Без тренда')
plt.plot(df['Дата'], df['Продажи_стационарные'], label='Стационарные')
plt.legend()
plt.title('Преобразованные продажи')
plt.xlabel('Дата')
plt.ylabel('Продажи')
plt.show()


Теперь у нас есть стационарный ряд Продажи_стационарные, который мы можем анализировать и моделировать.

Компоненты временных рядов


Временные ряды обычно состоят из трех основных компонентов: тренда, сезонности и шума (остатка). Понимание этих компонентов помогает нам лучше понимать структуру ряда и выбирать подходящие методы анализа.

1. Тренд:

Тренд — это долгосрочное изменение в данных, которое может быть восходящим (рост), нисходящим (падение) или горизонтальным (без изменений). Он представляет собой общее направление движения данных.

Посмотрим на примере:

# Создаем dataset с данными о продажах с трендом
data_trend = {'Дата': ['2023-01-01', '2023-02-01', '2023-03-01', '2023-04-01', '2023-05-01'],
              'Продажи': [1000, 1200, 1400, 1600, 1800]}

df_trend = pd.DataFrame(data_trend)

# Преобразуем столбец 'Дата' в формат даты
df_trend['Дата'] = pd.to_datetime(df_trend['Дата'])

# Построим график продаж с трендом
plt.plot(df_trend['Дата'], df_trend['Продажи'])
plt.title('Продажи с трендом (рост)')
plt.xlabel('Дата')
plt.ylabel('Продажи')
plt.show()


На графике видно, что продажи увеличиваются со временем. Это пример тренда восходящего направления.

2. Сезонность:

Сезонность — это периодические колебания в данных, которые повторяются через равные временные интервалы. Сезонность может быть годовой, месячной, недельной и т. д. Она связана с событиями, которые регулярно влияют на данные.

# Создаем dataset с данными о продажах с сезонностью
data_seasonal = {'Дата': ['2023-01-01', '2023-02-01', '2023-03-01', '2023-04-01', '2023-05-01'],
                 'Продажи': [1000, 1200, 900, 1100, 1400]}

df_seasonal = pd.DataFrame(data_seasonal)

# Преобразуем столбец 'Дата' в формат даты
df_seasonal['Дата'] = pd.to_datetime(df_seasonal['Дата'])

# Построим график продаж с сезонностью
plt.plot(df_seasonal['Дата'], df_seasonal['Продажи'])
plt.title('Продажи с сезонностью')
plt.xlabel('Дата')
plt.ylabel('Продажи')
plt.show()


На графике видно, что продажи имеют периодические колебания, которые повторяются примерно каждый месяц.

3. Шум:

Шум (остаток) — это случайные изменения в данных, которые не могут быть объяснены трендом или сезонностью. Он представляет собой нерегулярные колебания и вариации в данных.

# Создаем dataset с данными о продажах с шумом
data_noise = {'Дата': ['2023-01-01', '2023-02-01', '2023-03-01', '2023-04-01', '2023-05-01'],
              'Продажи': [1000, 1200, 1050, 1150, 1100]}

df_noise = pd.DataFrame(data_noise)

# Преобразуем столбец 'Дата' в формат даты
df_noise['Дата'] = pd.to_datetime(df_noise['Дата'])

# Построим график продаж с шумом
plt.plot(df_noise['Дата'], df_noise['Продажи'])
plt.title('Продажи с шумом')
plt.xlabel('Дата')
plt.ylabel('Продажи')
plt.show()


На графике видно, что продажи имеют случайные колебания, которые не имеют явного тренда или сезонности. Это пример шума.

Автокорреляция и частичная автокорреляция


Автокорреляция — это мера корреляции между временным рядом и его лагированными (отстающими) значениями. Это позволяет нам определить зависимость текущих значений от предыдущих.

Частичная автокорреляция — это мера корреляции между временным рядом и его лагированными значениями с учетом корреляции в промежуточных лагах. Она помогает выявить «чистую» зависимость от определенных отстающих значений, исключая влияние промежуточных лагов.

Представим, что у нас есть временной ряд данных о температуре каждый час:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf

# Создаем небольшой временной ряд температуры
np.random.seed(0)
dates = pd.date_range(start='2023-01-01', end='2023-01-07', freq='H')
temperature = np.random.normal(loc=25, scale=5, size=len(dates))
df_temperature = pd.DataFrame({'Дата': dates, 'Температура': temperature})

# Устанавливаем 'Дата' в качестве индекса
df_temperature.set_index('Дата', inplace=True)

# Построим график временного ряда
plt.figure(figsize=(12, 4))
plt.plot(df_temperature.index, df_temperature['Температура'])
plt.title('Временной ряд температуры')
plt.xlabel('Дата и время')
plt.ylabel('Температура (°C)')
plt.grid(True)
plt.show()

# Рассчитываем автокорреляцию и частичную автокорреляцию
plt.figure(figsize=(12, 6))
plt.subplot(211)
plot_acf(df_temperature['Температура'], lags=50, ax=plt.gca())
plt.title('Автокорреляция')

plt.subplot(212)
plot_pacf(df_temperature['Температура'], lags=50, ax=plt.gca())
plt.title('Частичная автокорреляция')

plt.tight_layout()
plt.show()




На графиках автокорреляции и частичной автокорреляции вы можете увидеть значимые лаги, которые могут помочь в выборе параметров модели для анализа и прогнозирования временного ряда температуры. Эти графики помогают определить структуру и зависимости в данных.

Примеры на практике


В этом разделе мы рассмотрим несколько практических примеров использования анализа временных рядов для решения разнообразных задач.

Прогнозирование продаж на основе временных рядов продаж электроники


В этом примере мы будем использовать данные о ежемесячных продажах электроники в магазине МВидео для прогнозирования будущих продаж.

Cоздадим небольшой dataset с данными о продажах. В качестве исходных данных предположим следующие продажи за последние два года:

import pandas as pd
import numpy as np

# Создаем даты с января 2022 года по декабрь 2023 года
dates = pd.date_range(start='2022-01-01', end='2023-12-31', freq='M')

# Генерируем случайные продажи в интервале от 1000 до 5000
sales = np.random.randint(1000, 5000, size=len(dates))

# Создаем DataFrame
sales_df = pd.DataFrame({'Дата': dates, 'Продажи': sales})

# Устанавливаем 'Дата' в качестве индекса
sales_df.set_index('Дата', inplace=True)

# Выводим первые несколько строк
print(sales_df.head())

Результат:

            Продажи
Дата               
2022-01-31     1355
2022-02-28     4665
2022-03-31     3154
2022-04-30     3490
2022-05-31     3569


Визуализация данных


Перед тем как перейти к прогнозированию, давайте визуализируем данные о продажах, чтобы понять их структуру и особенности.

import matplotlib.pyplot as plt

# Построим график продаж
plt.figure(figsize=(12, 6))
plt.plot(sales_df.index, sales_df['Продажи'], marker='o', linestyle='-')
plt.title('Продажи в магазине МВидео')
plt.xlabel('Дата')
plt.ylabel('Продажи')
plt.grid(True)
plt.show()


На графике видно, что у нас есть временной ряд продаж с некоторыми трендами и колебаниями.

Подготовка данных


Прежде чем перейти к прогнозированию, нам нужно подготовить данные. Это включает в себя обработку пропущенных значений и проверку стационарности ряда.

# Обработка пропущенных значений (если они есть)
sales_df.dropna(inplace=True)

# Проверка стационарности ряда
from statsmodels.tsa.stattools import adfuller

result = adfuller(sales_df['Продажи'])
print('ADF статистика:', result[0])
print('p-значение:', result[1])
print('Критические значения:')
for key, value in result[4].items():
    print(f'  {key}: {value}')

Результат:

ADF статистика: -3.336001912478917
p-значение: 0.013343910713214318
Критические значения:
  1%: -3.859073285322359
  5%: -3.0420456927297668
  10%: -2.6609064197530863

Если p-значение ниже некоторого порогового значения (обычно 0.05), то мы можем считать ряд стационарным.

Выбор и обучение модели


После подготовки данных мы можем выбрать и обучить модель для прогнозирования. Для этого примера мы будем использовать модель ARIMA.

from statsmodels.tsa.arima.model import ARIMA

# Обучение модели ARIMA
model = ARIMA(sales_df['Продажи'], order=(1, 1, 1))
model_fit = model.fit()

# Вывод статистики модели
print(model_fit.summary())

Результат:

                               SARIMAX Results                                
==============================================================================
Dep. Variable:                Продажи   No. Observations:                   24
Model:                 ARIMA(1, 1, 1)   Log Likelihood                -197.611
Date:                Mon, 18 Sep 2023   AIC                            401.222
Time:                        12:03:31   BIC                            404.628
Sample:                    01-31-2022   HQIC                           402.078
                         - 12-31-2023                                         
Covariance Type:                  opg                                         
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
ar.L1          0.1787      0.252      0.709      0.478      -0.315       0.673
ma.L1         -1.0000      0.349     -2.868      0.004      -1.683      -0.316
sigma2      1.469e+06   2.37e-07   6.19e+12      0.000    1.47e+06    1.47e+06
===================================================================================
Ljung-Box (L1) (Q):                   0.05   Jarque-Bera (JB):                 0.87
Prob(Q):                              0.82   Prob(JB):                         0.65
Heteroskedasticity (H):               0.70   Skew:                             0.34
Prob(H) (two-sided):                  0.63   Kurtosis:                         2.33
===================================================================================

Оценка качества прогноза


После обучения модели мы можем оценить ее качество на основе имеющихся данных.

from sklearn.metrics import mean_squared_error, mean_absolute_error

# Прогноз на основе обученной модели
forecast = model_fit.forecast(steps=12)

# Рассчитываем MSE и MAE
mse = mean_squared_error(sales_df['Продажи'][-12:], forecast)
mae = mean_absolute_error(sales_df['Продажи'][-12:], forecast)

print(f'MSE: {mse}')
print(f'MAE: {mae}')

Результат:

MSE: 1178795.2830408707
MAE: 906.8571433244668


Прогноз на будущее


Теперь, когда модель обучена и ее качество оценено, мы можем использовать ее для прогнозирования будущих значений.

# Прогноз на будущее (следующие 12 месяцев)
forecast_future = model_fit.forecast(steps=12)

# Создаем новый DataFrame для будущих значений
future_dates = pd.date_range(start='2024-01-01', periods=12, freq='M')
forecast_df = pd.DataFrame({'Дата': future_dates, 'Прогноз продаж': forecast_future})

# Присоединяем прогноз к исходному DataFrame
sales_df = sales_df.append(forecast_df, ignore_index=True)

# Визуализация исходных данных и прогноза
plt.figure(figsize=(12, 6))
plt.plot(sales_df.index[:-12], sales_df['Продажи'][:-12], label='Исходные данные')
plt.plot(sales_df.index[-12:], sales_df['Прогноз продаж'][-12:], label='Прогноз')
plt.title('Прогноз продаж в магазине МВидео')
plt.xlabel('Дата')
plt.ylabel('Продажи')
plt.legend()
plt.grid(True)
plt.show()



На графике показаны исходные данные о продажах и прогноз продаж на следующие 12 месяцев. Этот пример демонстрирует, как использовать анализ временных рядов для прогнозирования будущих продаж в магазине электроники.

Заключение


В данной статье мы рассмотрели основные концепции и методы работы с временными рядами в Python. Мы изучили, как импортировать данные временных рядов, визуализировать их, а также провели анализ стационарности и сезонности.

В следующей части нашей серии статей, мы погрузимся в более продвинутые аспекты работы с временными рядами. Мы рассмотрим, как использовать модели прогнозирования временных рядов, чтобы, например, прогнозировать погоду с учетом исторических данных. Мы также углубимся в анализ временных рядов с переменными интервалами, что может быть полезно в различных прикладных областях. Кроме того, мы уделим внимание совместным временным рядам, где мы сможем исследовать взаимосвязи между разными временными рядами и принимать более информированные решения.

Комментарии (1)


  1. economist75
    23.10.2023 07:20
    +8

    Хорошая статья. Добавлю что при работе с временными рядами наиболее логичный индекс у df - DateTime Index. И тогда вместо:

    subset = df[(df['Дата'] >= '2023-03-01') & (df['Дата'] <= '2023-04-30')]

    можно делать проще и наряднее:

    subset = df.loc['2023-03-01':'2023-04-30']