Серия случайных блужданий между начальными и конечными значениями и в пределах минимальных/максимальных пределов

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

Вот моя попытка сделать это, но по какой-то причине иногда серия превышает максимальные или ниже минимальных значений. Кажется, что значения Start и End соблюдаются, но не минимальное и максимальное значение. Как это можно исправить? Также я хотел бы дать стандартное отклонение для колебаний, но не знаю, как это сделать. Я использую randomPerc для колебаний, но это неправильно, так как вместо этого я хотел бы указать стандартное значение.

import numpy as np
import matplotlib.pyplot as plt

def generateRandomData(length,randomPerc, min,max,start, end):
    data_np = (np.random.random(length) - randomPerc).cumsum()
    data_np *= (max - min) / (data_np.max() - data_np.min())
    data_np += np.linspace(start - data_np[0], end - data_np[-1], len(data_np))
    return data_np

randomData=generateRandomData(length = 1000, randomPerc = 0.5, min = 50, max = 100, start = 66, end = 80)

## print values
print("Max Value",randomData.max())
print("Min Value",randomData.min())
print("Start Value",randomData[0])
print("End Value",randomData[-1])
print("Standard deviation",np.std(randomData))

## plot values
plt.figure()
plt.plot(range(randomData.shape[0]), randomData)
plt.show()
plt.close()

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

 ## generate 1000 series and check if there are any values over the maximum limit or under the minimum limit
    for i in range(1000):
        randomData = generateRandomData(length = 1000, randomPerc = 0.5, min = 50, max = 100, start = 66, end = 80)
        if(randomData.min() < 50):
            print(i, "Value Lower than Min limit")
        if(randomData.max() > 100):
            print(i, "Value Higher than Max limit")

person RaduS    schedule 26.10.2017    source источник
comment
Вау... это имя функции звучит слишком громко. Вы видели рекомендации PEP? Вы должны сделать их короткими и осмысленными.   -  person cs95    schedule 26.10.2017
comment
хм, хорошая мысль, переименую :)   -  person RaduS    schedule 26.10.2017
comment
Позвольте мне понять. В вашем коде вы хотите сгенерировать случайное блуждание из 1000 шагов, начиная с 66 и заканчивая 80. Размеры шагов должны быть равномерно распределены на [50, 100]. Что такое randomPerc?   -  person Bill Bell    schedule 26.10.2017
comment
Да, ты прав. randomPerc — это моя попытка определить, насколько он колеблется, и находится в диапазоне от 0,25 до 0,5. Но вместо этого я хотел бы указать стандартное отклонение или процент от 0 до 1.   -  person RaduS    schedule 26.10.2017
comment
Мне кажется, что у вас не может быть всего этого. Если вы суммируете 1000 шагов, равномерно распределенных по [50, 100], начиная с 66, вероятность окончания на 80 равна нулю.   -  person Bill Bell    schedule 26.10.2017
comment
Похоже, вам нужен броуновский мост, но с дополнительными ограничениями на стохастический процесс.   -  person Warren Weckesser    schedule 26.10.2017
comment
как здесь можно применить этот броуновский мост?   -  person RaduS    schedule 26.10.2017


Ответы (4)


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

def bounded_random_walk(length, lower_bound,  upper_bound, start, end, std):
    assert (lower_bound <= start and lower_bound <= end)
    assert (start <= upper_bound and end <= upper_bound)

    bounds = upper_bound - lower_bound

    rand = (std * (np.random.random(length) - 0.5)).cumsum()
    rand_trend = np.linspace(rand[0], rand[-1], length)
    rand_deltas = (rand - rand_trend)
    rand_deltas /= np.max([1, (rand_deltas.max()-rand_deltas.min())/bounds])

    trend_line = np.linspace(start, end, length)
    upper_bound_delta = upper_bound - trend_line
    lower_bound_delta = lower_bound - trend_line

    upper_slips_mask = (rand_deltas-upper_bound_delta) >= 0
    upper_deltas =  rand_deltas - upper_bound_delta
    rand_deltas[upper_slips_mask] = (upper_bound_delta - upper_deltas)[upper_slips_mask]

    lower_slips_mask = (lower_bound_delta-rand_deltas) >= 0
    lower_deltas =  lower_bound_delta - rand_deltas
    rand_deltas[lower_slips_mask] = (lower_bound_delta + lower_deltas)[lower_slips_mask]

    return trend_line + rand_deltas

randomData = bounded_random_walk(1000, lower_bound=50, upper_bound =100, start=50, end=100, std=10)

Вы можете видеть это как решение геометрической задачи. trend_line соединяет ваши точки start и end и имеет поля, определяемые lower_bound и upper_bound. rand — ваше случайное блуждание, rand_trend — линия тренда и rand_deltas — отклонение от rand линии тренда. Мы совмещаем линии тренда и хотим убедиться, что дельты не превышают поля. Когда rand_deltas превышает допустимое поле, мы «складываем» лишнее обратно к границам.

В конце вы добавляете полученные случайные дельты к линии тренда start=>end, таким образом получая желаемое ограниченное случайное блуждание.

Параметр std соответствует величине дисперсии случайного блуждания.

обновление: исправлены утверждения

В этой версии "std" не обещает быть "интервалом".

person igrinis    schedule 29.10.2017
comment
Вот и все, это именно то, что я хотел, и это векторизовано. Спасибо @igrinis, ваше решение потрясающее :) - person RaduS; 30.10.2017
comment
И последнее, он выдает ошибку при использовании одного и того же нижнего и верхнего уровней в качестве начала и конца. Бывают ситуации, когда минимальные и максимальные пределы совпадают с начальным и конечным, а серия вообще не переполняется. Как бы вы подошли к ним? Есть ли какие-либо модификации, которые можно сделать? - person RaduS; 31.10.2017
comment
Это возможно, но тогда вы теряете контроль над дисперсией псевдослучайного блуждания. Смотрите обновленный код. - person igrinis; 31.10.2017
comment
Это идеально, спасибо за обновление кода, это потрясающий ответ :) - person RaduS; 02.11.2017

Я заметил, что вы использовали встроенные функции в качестве аргументов (min и max), что не рекомендуется (я изменил их на max_1 и min_1). Кроме этого, ваш код должен работать так, как ожидалось:

def generateRandomData(length,randomPerc, min_1,max_1,start, end):
    data_np = (np.random.random(length) - randomPerc).cumsum()
    data_np *= (max_1 - min_1) / (data_np.max() - data_np.min())
    data_np += np.linspace(start - data_np[0], end - data_np[-1],len(data_np))
    return data_np
randomData=generateRandomData(1000, 0.5, 50, 100, 66, 80)

Если вы хотите изменить свой код, это будет работать:

import random
for_fill=[]
# generate 1000 samples within the specified range and save them in for_fill
for x in range(1000):
    generate_rnd_df=random.uniform(50,100)
    for_fill.append(generate_rnd_df)
#set starting and end point manually
for_fill[0]=60
for_fill[999]=80
person xan    schedule 26.10.2017
comment
Хей, Ксан, хорошая мысль, но если вы запустите генератор в течение многих итераций, вы заметите, что он по-прежнему создает значения выше максимальных или ниже минимальных значений. Это именно то, чего я пытаюсь избежать - person RaduS; 26.10.2017
comment
Я вижу, что вы отредактировали ответ, но я все еще не понимаю, как это создает серию случайных блужданий, которая соответствует заданным требованиям. - person RaduS; 26.10.2017

Вот один из способов, очень грубо выраженный в коде.

>>> import random
>>> steps = 1000
>>> start = 66
>>> end = 80
>>> step_size = (50,100)

Создайте 1000 шагов, гарантированно находящихся в требуемом диапазоне.

>>> crude_walk_steps = [random.uniform(*step_size) for _ in range(steps)]
>>> import numpy as np

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

>>> crude_walk = np.cumsum(crude_walk_steps)
>>> min(crude_walk)
57.099056617839288
>>> max(crude_walk)
75048.948693623403

Рассчитайте простое линейное преобразование для масштабирования шагов.

>>> from sympy import *
>>> var('a b')
(a, b)
>>> solve([57.099056617839288*a+b-66,75048.948693623403*a+b-80])
{b: 65.9893403510312, a: 0.000186686954219243}

Масштабирует шаги.

>>> walk = [0.000186686954219243*_+65.9893403510312 for _ in crude_walk]

Убедитесь, что прогулка теперь начинается и останавливается там, где предполагалось.

>>> min(walk)
65.999999999999986
>>> max(walk)
79.999999999999986
person Bill Bell    schedule 26.10.2017
comment
Привет, Билл, я реализовал решение, но я не получаю результат, который мне нужен, я просто получаю прямую линию - person RaduS; 26.10.2017
comment
Координаты y нет. - person Bill Bell; 26.10.2017
comment
Вы неправильно поняли вопрос. @RaduS хочет начать обход с определенного значения start, закончить его значением end и оставить его между границами min и max. - person igrinis; 30.10.2017
comment
@igrinis: И я бы сказал, что если достаточное количество людей предложит предположения, один из них получит ответ, соответствующий его воображению. - person Bill Bell; 30.10.2017

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

Код ниже создает бесконечный поток «действительных» случайных блужданий. Будьте осторожны с очень жесткими ограничениями, «следующий» вызов может занять некоторое время;).

import itertools
import numpy as np


def make_random_walk(first, last, min_val, max_val, size):
    # Generate a sequence of random steps of lenght `size-2`
    # that will be taken bewteen the start and stop values.
    steps = np.random.normal(size=size-2)

    # The walk is the cumsum of those steps
    walk = steps.cumsum()

    # Performing the walk from the start value gives you your series.
    series = walk + first

    # Compare the target min and max values with the observed ones.
    target_min_max = np.array([min_val, max_val])
    observed_min_max = np.array([series.min(), series.max()])

    # Calculate the absolute 'overshoot' for min and max values
    f = np.array([-1, 1])
    overshoot = (observed_min_max*f - target_min_max*f)

    # Calculate the scale factor to constrain the walk within the
    # target min/max values.
    # Don't upscale.
    correction_base = [walk.min(), walk.max()][np.argmax(overshoot)]
    scale = min(1, (correction_base - overshoot.max()) / correction_base)

    # Generate the scaled series
    new_steps = steps * scale
    new_walk = new_steps.cumsum()
    new_series = new_walk + first

    # Check the size of the final step necessary to reach the target endpoint.
    last_step_size = abs(last - new_series[-1]) # step needed to reach desired end

    # Is it larger than the largest previously observed step?
    if last_step_size > np.abs(new_steps).max():
        # If so, consider this series invalid.
        return None
    else:
        # Else, we found a valid series that meets the constraints.
        return np.concatenate((np.array([first]), new_series, np.array([last])))


start = 66
stop = 80
max_val = 100
min_val = 50
size = 1000

# Create an infinite stream of candidate series
candidate_walks = (
    (i, make_random_walk(first=start, last=stop, min_val=min_val, max_val=max_val, size=size))
    for i in itertools.count()
)
# Filter out the invalid ones.
valid_walks = ((i, w) for i, w in candidate_walks if w is not None)

idx, walk = next(valid_walks)  # Get the next valid series
print(
    "Walk #{}: min/max({:.2f}/{:.2f})"
    .format(idx, walk.min(), walk.max())
)
person NSteinhoff    schedule 29.10.2017
comment
Спасибо NSteinhoff за решение :) - person RaduS; 30.10.2017