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

ПРИМЕЧАНИЕ. Сообщение выглядит длиннее, чем должно быть, из-за строк документации и массива, состоящего из 40 дат и времени.

У меня есть данные временного ряда. Для примера предположим, что у меня есть три параметра, каждый из которых состоит из 40 точек данных: даты и времени (данные 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).

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


person Community    schedule 07.05.2018    source источник


Ответы (2)


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

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 увеличиваются вместе), результат выглядит следующим образом:

Результат контура

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

Изменить: я загрузил полный пример на repl.it.

person Axel    schedule 07.05.2018
comment
Я немного поиграю с этим, спасибо за полный пример. - person ; 08.05.2018
comment
Когда вы вызываете mod_dictionary = subsect(data_dictionary, indices=slice(i, i+18, 1)) в цикле for, обратите внимание, что временной интервал остается постоянным. Если бы можно было сделать все возможные комбинации индексных срезов, то можно было бы создать контурный график по верхнему левому треугольнику сетки (поскольку нижний правый треугольник сетки будет состоять из точек, в которых начальная дата-время будет позже конечной даты и времени). Это также устранит необходимость интерполировать значения. . - person ; 08.05.2018
comment
Я пытался вложить цикл for в цикл while, который итеративно корректировал границы (безуспешно), ваше решение намного элегантнее! Спасибо. - person ; 08.05.2018

Используя решение, опубликованное @Axel, я смог построить контурный график без использования griddata и pandas. (Мне нужно отредактировать метки, но это не моя забота. Прошедшие часы из исходного словаря можно использовать в качестве индексов для нарезки массива даты и времени для этой цели). Преимущество этого подхода в том, что интерполяция не требуется, а использование векторизации 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()
person Community    schedule 08.05.2018