Как создать вложенные взвешенные распределения в Python/numpy?

#python #numpy #montecarlo

Вопрос:

Я пытаюсь оптимизировать (векторизировать?) Создание моделирования в стиле Монте-Карло, и я не смог понять, как создавать вложенные взвешенные случайные значения с использованием numpy или аналогичных библиотек. Рассмотрим сценарий, вдохновленный interviewqs вопросом: «Учащиеся в трех классах должны проголосовать за одного из двух кандидатов в президенты класса. В классе А 40% учащихся, и они разделены 50/50 на кандидатов X и Y. В имеет 25% студентов и делится на 60/40. C насчитывает 35% студентов и делится на 35/65»

Создание этих данных с помощью ванильного Python может выглядеть примерно так,

 import random

nsimulations = 10_000_000

def choose_classroom():
    "returns A, B, or C based on percentages"
    value = random.random()
    if value < .4:
        return 'A'
    elif value < .65:
        return 'B'
    else:
        return 'C'
        
def choose_support(classroom):
    "return X or Y based on support percentage by classroom"
    value = random.random()
    if classroom == 'A':
        return "X" if value < .5 else "Y"
    elif classroom == 'B':
        return "X" if value < .6 else "Y"
    elif classroom == 'C':
        return "X" if value < .35 else "Y"
        
results = []
for i in range(nsimulations):
    classroom = choose_classroom()
    support = choose_support(classroom)
    results.append({'classroom': classroom, 'support': support})
 

Для запуска 10-метрового моделирования на моей машине требуется около 10 секунд. Я бы хотел улучшить это время. Первое, на что я посмотрел, был тупой.случайный.выбор, fast_classrooms = np.random.choice(['A', 'B', 'C'], size=nsimulations, p=[.4, .25, .35]) . Это действительно выполняется быстро, около 350 мс. Однако я не знаю, как оттуда генерировать последующие X / Y дистрибутивы.

Одна вещь , которую я пробовал, — это Панды apply , и, похоже, она выполняет какие-то оптимизации под прикрытием. Приведенный ниже фрагмент кода Pandas выполняется за ~2,5 секунды, в то время как понимание списка ( [choose_support(record) for record in fast_classrooms] занимает ~4 секунды. Тем не менее, мне кажется, что это не «правильный» способ делать вещи.

 import pandas
import numpy as np

fast_classrooms = np.random.choice(['A', 'B', 'C'], size=nsimulations, p=[.4, .25, .35])
df = pandas.DataFrame({'classroom': fast_classrooms})
df['support'] = df.classroom.apply(choose_support)
 

То, что я надеялся найти, — это что-то, что могло бы генерировать вложенные взвешенные распределения, что-то вроде — np.random.choice([['A', 'B', 'C'], ['X', 'Y']], p=[[.4, .25, .35], [[.5, .5], [.6, .4], [.35, .65]]])

Каковы некоторые способы получения этих данных?

Ответ №1:

Вы можете использовать np.random.choice на парах вместо того, чтобы запускать функцию дважды. Это означает, что вы можете рассчитать вероятность наличия пары («класс», «поддержка»). Например, вероятность выбора класса » А «И получения поддержки «Х» составляет 0,4*0,5 = 0,2 и так далее.

Приведенный ниже код работает для меня довольно быстро.

 import numpy as np
nsimulations = 10000000

#Construct the probabilities and pairs 
p = [.4*.5, .4*.5, .25*.6, .25*.4, .35*.35, .35*.65]
pairs = [{'classroom': 'A', 'support': 'X'}, 
         {'classroom': 'A', 'support': 'Y'},
         {'classroom': 'B', 'support': 'X'},
         {'classroom': 'B', 'support': 'Y'},
         {'classroom': 'C', 'support': 'X'},
         {'classroom': 'C', 'support': 'Y'}]

# Sample the pairs based on the probabilities
fast_classrooms = np.random.choice(pairs, size=nsimulations, p=p)
 

Редактировать:

Этот код был взят 0.6193864345550537 seconds по сравнению с методом, опубликованным OP с. 7.815439224243164 seconds Также подтверждено @Tom McLean в комментарии.

Комментарии:

1. Именно так я и думал, но не мог придумать, как это реализовать

2. Это такой элегантный ответ! Я хотел бы добавить, что можно использовать itertools.product для получения декартова произведения (комбинации X и Y и вероятностей), если у вас слишком много пар для ввода вручную.

3. @iffanh Мне очень нравится этот ответ, он такой умный! Есть ли быстрый способ прочитать множество словарей в панд, чтобы classroom и support они были в своих собственных столбцах? pandas.DataFrame(fast_classrooms) выполняется быстро, но дает мне один столбец словарей. Пробует DataFrame(fast_classrooms.tolist()) или DataFrame.from_records(fast_classroom) разбивает его на столбцы, но занимает несколько секунд

4. Для справки этот метод занял 0,42 с на моей машине для моделирования 10 м против 7,7 с из метода, описанного в вопросе

5. @FBruzzesi Спасибо за предложение. Добавлено 🙂

Ответ №2:

Вы можете значительно сократить количество циклов python, сделав код более векторизованным:

 import numpy as np

nsimulations = 12
uniquerooms = ['A','B','C']
supportprobs = [0.5, 0.6, 0.35]
classrooms = np.random.choice(uniquerooms, size=nsimulations, p=[.4, .25, .35])
supports = np.empty_like(classrooms, dtype=classrooms.dtype)
for room, prob in zip(uniquerooms, supportprobs):
    mask = classrooms == room
    supports[mask] = np.random.choice(['X','Y'], size=mask.sum(), p=[prob, 1-prob])

print(classrooms)
# ['C' 'A' 'C' 'A' 'C' 'C' 'A' 'C' 'B' 'C' 'B' 'A']
print(supports)
# ['X' 'Y' 'Y' 'Y' 'Y' 'X' 'Y' 'X' 'X' 'X' 'Y' 'X']
 

Ответ №3:

Я думаю, что текущий лучший ответ является самым элегантным, но я хочу добавить numpy.по частям в смесь из-за расширяемости условий:

 import numpy as np 

fast_classrooms = np.random.choice(['A', 'B', 'C'], size=nsimulations, p=[.4, .25, .35])

np.piecewise(fast_classrooms, [fast_classrooms == 'A', fast_classrooms =='B', fast_classrooms=='C'], 
             [lambda X: "X" if np.random.random() < .5 else "Y",
              lambda X: "X" if np.random.random() < .6 else "Y",
              lambda X: "X" if np.random.random() < .35 else "Y"
             ])

out: array(['X', 'X', 'Y', ..., 'Y', 'X', 'X']
 

~660 мс на 10-миллиметровом моделировании на моей машине, YMMV

Комментарии:

1. Спасибо @Michael Karman. Я думаю np.random.random() , что это может быть рассчитано только один раз в ваших лямбда-функциях. Когда я читаю в fast_classrooms кадре данных и out в него, все A они имеют одинаковое X Y значение или и т. Д.