Раскрытие возможностей анализа временных рядов Python: выявление скрытых тенденций среди потока данных, выявление иголок в стоге сена временных рядов.

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

Введение

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

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

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

Часть 1. Извлечение данных из VortexaSDK

В этой статье мы будем использовать данные Vortexa в качестве источника данных.

Россия активно участвует в деятельности STS и с прошлого года находится в центре внимания рынка. В этой статье мы проанализируем последние тенденции в деятельности российских СТС в качестве нашего примера.

Мы будем использовать данные Vortexa Cargo Movements для определения тенденций активности STS. Приведенный ниже код используется для загрузки грузов дизельного топлива из России в период с 1 декабря 2022 г. по 31 марта 2023 г., извлечения перемещений, содержащих события STS, и их группировки по месяцу, в котором имело место STS.

# Extract data from CargoMovement Endpoint
df1 = v.CargoMovements().search(
    filter_activity= 'loading_end',
    filter_time_min = datetime(2022,12,1),
    filter_time_max = datetime(2023,3,31,23,59,59),
    filter_products = diesel,
    filter_origins = russia
    ).to_df(columns = 'all')

# Filter for CargoMovements that engage in STS
df1_sts = df1[df1['events.cargo_sts_event.0.from_vessel_id'] != “”]

# Group by STS start date in a monthly basis
df_ts =df1_sts.set_index('events.cargo_sts_event.0.start_timestamp')\
  .groupby([pd.Grouper(freq='MS'),
  'events.cargo_sts_event.0.location.sts_zone.label'
  ])\
  .agg({'quantity':'sum'})\
  .unstack(level=1).fillna(0).stack().reset_index()

# Analyze trend before end of Mar 23
df_ts_filter = df_ts[
  df_ts['transit_month']<datetime(2023,3,31,23,59,tzinfo=timezone.utc)
]

Часть 2. Очистка данных для анализа верхнего местоположения STS

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

# Filter for top 7 STS locations
top_7=df_ts_filter.groupby('sts_zone')\
  .agg({'quantity':'sum'})\
  .sort_values('quantity',ascending = False)\
  .index[:7].tolist()

# Filter out sts zones which has only one STS event (exclude list)
top_7 = list(set(top_7) - set(exclude_list))

Обнаружение временных рядов

Метод 1. Линейная регрессия

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

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

* Код для линейной регрессии см. в приложении.

# Perform Linear Regression and calculate the gradient/slope
lr_df = calculate_slope(df_ts_filter,location_col = 'sts_zone',quantity_col = 'quantity', location_list = top_location)

# Visualizing the output
lr_df[['r2_score']].style.bar(subset = 'r2_score',align='left', color=['#d65f5f', '#5fba7d'])
lr_df[['mape']].style.bar(subset = 'mape',align='right', color=['#d65f5f', '#5fba7d'])
lr_df[['gradient']].style.bar(subset = 'gradient',align='mid', color=['#d65f5f', '#5fba7d'])

Чтобы проверить результат, мы сначала смотрим на оценку R² и MAPE результата нашей подобранной регрессии. Оценка R² измеряет, насколько хорошо модель линейной регрессии соответствует данным. Он представляет собой долю дисперсии зависимой переменной, которая может быть объяснена независимыми переменными в модели.

Более высокий показатель R² указывает на лучшее соответствие между моделью и данными.

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

Более низкий показатель MAPE указывает на то, что модель дает более точные прогнозы.

В нашем случае Kalamata STS, Augusta STS и Taman STS имеют как высокий показатель R², так и низкий MAPE, поэтому результат регрессии имеет более высокую достоверность.

Из приведенных выше результатов наклона/градиента видно, что STS Каламата [GR] в последнее время становится самым жарким местом STS, за ним следует зона STS Августа и, наконец, STS Тамань [RU], так как все они имеют высокие уклоны.

Метод 2. Статистика Кендалла Тау

Тау Кендалла измеряет силу связи между двумя переменными путем сравнения количества согласованных и несогласованных пар наблюдений. В контексте данных временных рядов это означает сравнение порядка значений измеряемой переменной в разные моменты времени.

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

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

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

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

*Код для тестирования относительного порядка см. в приложении

# Compute Tau statistics
tau_df = calculate_tau(df_ts_filter,location_col = 'sts_zone',quantity_col = 'quantity', location_list = top_location)

# Visualizing the output
tau_df[['pvalue']].style.bar(subset = 'pvalue',align='right', color=['#d65f5f', '#5fba7d'])
tau_df[['tau']].style.bar(subset = 'tau',align='mid', color=['#d65f5f', '#5fba7d'])

Нам нужно выбрать порог (альфа) для p-значений, возвращаемых тау Кендалла. Если p-значение меньше выбранной нами альфы, мы отклоняем нулевую гипотезу и принимаем альтернативную гипотезу о том, что в ряду существует значительная тенденция. Увеличивая альфа-уровень, мы, по сути, допускаем большую вероятность совершения ошибки типа I — другими словами, обнаружения тенденции, которой на самом деле не существует. Однако мы будем использовать альфа-уровень 0,1, как мы это делаем. не хотите пропустить какие-либо интересные тенденции.

Р-значения Кендалла тау (рис. 4) предполагают наличие значительных тенденций в STS Августы [IT], STS Тамани [RU] и STS Каламата [GR]. Судя по их тау-статистике (рис. 4), все они приближаются к 1, а это означает, что во всех трех зонах STS наблюдаются повышательные тенденции.

Подтверждение результатов

На приведенной выше диаграмме показано, что все эти три местоположения STS, полученные двумя вышеуказанными методами, на самом деле имеют очевидную тенденцию к увеличению. Проверив другие зоны sts, ни одна из них не имеет более очевидного возрастающего тренда, чем эти три. Это помогло проверить способность этих двух методов обнаруживать тенденции во временных рядах. Однако есть некоторые параметры, которые вам может потребоваться учитывать, такие как уровень значимости (альфа) и порог MAPE, чтобы снизить риск обнаружения ложных трендов.

Заключение

Используя два разных метода — линейную регрессию и тау-тест Кендалла, мы успешно показали наличие недавнего тренда активности STS в трех зонах STS, а также получили величину тренда. Наши результаты были подтверждены путем изучения значения R2 и значения MAPE модели линейной регрессии и тау-коэффициента Кендалла, которые указывают на значительную тенденцию в данных временного ряда. Вышеуказанные методы могут значительно сэкономить время аналитиков, автоматически обнаруживая важные тенденции в мире энергетики, поскольку на этом рынке существуют миллионы комбинаций маршрутов и продуктов.

Приложение

Линейная регрессия

import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, r2_score

def calculate_slope(df,location_col,quantity_col,location_list):
    '''
        This function will loop through the location list and calculate slope of best fit of linear regression.
        
    Parameters:
    df: time series dataframe
    location_col: the group by location column name
    quantity_col: aggregated quantity column name by certain frequency
    location_list: Location to loop through
    
    '''
    location_dict = {}
    for location in location_list:
        df2 = df[df[location_col] ==  location].reset_index(drop = True)
        df2[quantity_col] = df2[quantity_col].replace('',0)
        # Extract the time values and reshape them to a 2D array
        X = df2.index.values.reshape(-1, 1)

        # Extract the time series values and reshape them to a 1D array
        y = df2[quantity_col].values.reshape(-1, 1)
        
        # Fit a linear regression model to the data
        reg = LinearRegression().fit(X, y)

        # Get the prediction
        y_pred = reg.predict(X)
        
        # Get the fitting metrics
        mape = mean_absolute_error(y, y_pred)
        r2 = r2_score(y,y_pred)
        
        # Get the slope of the trend line
        slope = reg.coef_[0][0]
        
        mean = np.mean(y)
        
        # Store each location - gradient pairs into dictionary
        location_dict[location] = (slope,mape,mean,r2)
        lr_df = pd.DataFrame(location_dict).transpose()
        lr_df = lr_df.rename(columns = {0:'gradient',1:'mae',2:'mean',3:'r2_score'})
        lr_df['mape'] = lr_df['mae']/lr_df['mean']
    return lr_df

def visualize_location_gradient(df):
    '''
    Take location - gradient pairs and give bar chart visualization.
    '''
    df = df.sort_values('gradient',ascending = False)
    fig = px.bar(df,x = df.index, y = 'gradient', title = 'Gradient for each location')
    fig.show()

Статистика Кендалла Тау

import scipy.stats as stats

def calculate_tau(df,location_col,quantity_col,location_list):
    '''
        This function will loop through the location list and compute tau statistics for each zone.
        
    Parameters:
    df: time series dataframe
    location_col: the group by location column name
    quantity_col: aggregated quantity column name by certain frequency
    location_list: Location to loop through
    
    '''
    location_pvalue_dict = {}
    for location in location_list:
        df2 = df[df[location_col] ==  location].reset_index(drop = True)
        df2[quantity_col] = df2[quantity_col].replace('',0)
        # Extract the time values and reshape them to a 2D array
        X = df2.index.values.reshape(-1, 1)

        # Extract the time series values and reshape them to a 1D array
        y = df2[quantity_col].values.reshape(-1, 1)
        
        # Compute tau statistics
        tau, p_value = stats.kendalltau(X, y)
        
        # Store each location - gradient pairs into dictionary
        location_pvalue_dict[location] = (tau, p_value)
        tau_df = pd.DataFrame(location_pvalue_dict).transpose()
        tau_df = tau_df.rename(columns = {0:'tau',1:'pvalue'})
    return tau_df