Как можно инициализировать данные для контурного графика, используя функцию, которая принимает один вход и выдает скалярное значение? - PullRequest
0 голосов
/ 07 мая 2018

Примечание: Пост выглядит длиннее, чем следовало бы из-за строк документации и массива, состоящего из 40 datetime.

У меня есть данные временных рядов. Для примера, скажем, у меня есть три параметра, каждый из которых состоит из 40 точек данных: datetime (задается dts), скорость (задается vobs) и истекший час (задается els), которые объединяются по ключу в словарь data_dict.

dts = np.array(['2006/01/01 02:30:04', '2006/01/01 03:30:04', '2006/01/01 03:54:04'
 ,'2006/01/01 05:30:04', '2006/01/01 06:30:04', '2006/01/01 07:30:04'
 ,'2006/01/01 08:30:04', '2006/01/01 09:30:04', '2006/01/01 10:30:04'
 ,'2006/01/01 11:30:04', '2006/01/01 12:30:04', '2006/01/01 13:30:04'
 ,'2006/01/01 14:30:04', '2006/01/01 15:30:04', '2006/01/01 16:30:04'
 ,'2006/01/01 17:30:04', '2006/01/01 18:30:04', '2006/01/01 19:30:04'
 ,'2006/01/01 20:30:04', '2006/01/01 21:30:04', '2006/01/01 21:54:05'
 ,'2006/01/01 23:30:04', '2006/01/02 00:30:04', '2006/01/02 01:30:04'
 ,'2006/01/02 02:30:04', '2006/01/02 03:30:04', '2006/01/02 04:30:04'
 ,'2006/01/02 05:30:04', '2006/01/02 06:30:04', '2006/01/02 07:30:04'
 ,'2006/01/02 08:30:04', '2006/01/02 09:30:04', '2006/01/02 10:30:04'
 ,'2006/01/02 11:30:04', '2006/01/02 12:30:04', '2006/01/02 13:30:04'
 ,'2006/01/02 14:30:04', '2006/01/02 15:30:04', '2006/01/02 16:30:04'
 ,'2006/01/02 17:30:04'])

vobs = np.array([158, 1, 496, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
    , 1, 1, 823, 1, 1, 1, 1, 303, 1, 1, 1, 1, 253, 1, 1, 1, 408, 1
    , 1, 1, 1, 321])

els = np.array([i for i in range(len(vobs))])

data_dictionary = {'datetime' : dts, 'values' : vobs, 'elapsed' : els}

У меня есть функция, которая принимает словарь в качестве входных данных и выводит единственное скалярное значение type <float> или type <int>. Функция, приведенная ниже, проще, чем мой фактический пример использования, и приведена для примера.

def get_z(dictionary):
    """ This function returns a scalar value. """
    return np.sum(dictionary['elapsed'] / dictionary['values'])

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

def subsect(dictionary, indices):
    """ This function returns a dictionary, the array values
        of which are sliced at the input indices. """
    return {key : dictionary[key][indices] for key in list(dictionary.keys())}

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

def read_dictionary(dictionary):
    """ This function prints the input dictionary as a check. """
    for key in list(dictionary.keys()):
        print(" .. KEY = {}\n{}\n".format(key, dictionary[key]))

print("\nORIGINAL DATA DICTIONARY\n")
read_dictionary(data_dictionary)

# for i in range(1, 38):
    # mod_dictionary = subsect(data_dictionary, indices=slice(i, 39, 1))
    # print("\n{}th MODIFIED DATA DICTIONARY\n".format(i))
    # read_dictionary(mod_dictionary)

Моя проблема в том, что мне нужен контурный сюжет. Ось X будет содержать нижнюю границу интервала дата-время (первая запись mod_dictionary[i]), а ось Y будет содержать верхнюю границу интервала дата-время (последняя запись mod_dictioary[i]). Обычно при построении контурного графика имеется массив значений (x,y), которые превращаются в сетку (X,Y) через numpy.meshgrid. Поскольку моя фактическая функция (не та, что в примере) не векторизована, я могу использовать X.copy().reshape(-1) и изменить свой результат обратно, используя (...).reshape(X.shape).

Моя точная проблема в том, что я не знаю, как создать сетку с различными параметрами, используя один словарь в качестве входных данных для функции, которая выводит одно скалярное значение. Есть ли способ сделать это?

Ответы [ 2 ]

0 голосов
/ 08 мая 2018

Используя решение, опубликованное @ Axel , я смог создать контурный график без использования griddata и pandas. (Мне нужно отредактировать метки, но это не моя проблема. Истекшие часы из исходного словаря могут использоваться в качестве индексов для нарезки массива datetime для этой цели). Преимущество этого подхода состоит в том, что интерполяция не требуется, и использование векторизации NumPy превосходит скорость, полученную с использованием двойного цикла for.

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker

def initialize_xy_grid(data_dictionary):
    """ """
    params = {'x' : {}, 'y' : {}}
    params['x']['datetime'] = data_dictionary['datetime'][:-1]
    params['x']['elapsed'] = data_dictionary['elapsed'][:-1]
    params['y']['datetime'] = data_dictionary['datetime'][1:]
    params['y']['elapsed'] = data_dictionary['elapsed'][1:]
    X_dt, Y_dt = np.meshgrid(params['x']['datetime'], params['y']['datetime'])
    X_hr, Y_hr = np.meshgrid(params['x']['elapsed'], params['y']['elapsed'])
    return X_hr, Y_hr, X_dt, Y_dt

def initialize_z(data_dictionary, X, Y):
    """ """
    xx = X.copy().reshape(-1)
    yy = Y.copy().reshape(-1)
    return np.array([get_z(subsect(data_dictionary, indices=slice(xi, yi, 1))) for xi, yi in zip(xx, yy)])

def initialize_Z(z, shape):
    """ """
    return z.reshape(shape)

X_hr, Y_hr, X_dt, Y_dt = initialize_xy_grid(data_dictionary)
z = initialize_z(data_dictionary, X_hr, Y_hr)
Z = initialize_Z(z, X_hr.shape)

ncontours = 11
plt.contourf(X_hr, Y_hr, Z, ncontours, cmap='plasma', )
contours = plt.contour(X_hr, Y_hr, Z, ncontours, colors='k')
fmt_func = lambda x, pos : "{:1.3f}".format(x)
fmt = matplotlib.ticker.FuncFormatter(fmt_func)
plt.clabel(contours, inline=True, fontsize=8, fmt=fmt)
plt.show()
0 голосов
/ 08 мая 2018

Если я правильно понял вашу идею, тогда это должно быть то, что вам нужно. Однако мне понадобились следующие пакеты:

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.mlab import griddata
import pandas as pd

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

lower_bounds = [];
upper_bounds = [];
z_values = [];
for j in range(1, 30):
  for i in range(0,j):
    mod_dictionary = subsect(data_dictionary, indices=slice(i, j, 1))
    lower_bounds.append(mod_dictionary['datetime'][0])
    upper_bounds.append(mod_dictionary['datetime'][-1])
    z_values.append(get_z(mod_dictionary))

Затем строки даты и времени преобразуются в Timestamps:

lower_bounds_dt = [pd.Timestamp(date).value for date in lower_bounds]
upper_bounds_dt = [pd.Timestamp(date).value for date in upper_bounds]

И генерируется сетка для контурного графика:

xi = np.linspace(min(lower_bounds_dt), max(lower_bounds_dt), 100)
print(xi)
yi = np.linspace(min(upper_bounds_dt), max(upper_bounds_dt), 100)
print(yi)

Используя griddata, генерируются недостающие точки сетки для значений z.

zi = griddata(lower_bounds_dt, upper_bounds_dt, z_values, xi, yi)
print(zi)

Наконец, вы можете использовать contour или contourf для генерации контурного графика:

fig1 = plt.figure(figsize=(10, 8))
ax1 = fig1.add_subplot(111)
ax1.contourf(xi, yi, zi)
fig1.savefig('graph.png')

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

Result of contourf

Вы можете легко изменить это, изменив способ распределения массивов данных в цикле for. Используя pd.to_datetime, вы также можете отобразить оси x и y в предпочитаемом вами формате даты и времени.

Редактировать: Я загрузил полный пример в repl.it

...