Используйте numpy для генерации случайных данных с условием case-when - PullRequest
2 голосов
/ 10 января 2020

Вопрос

Я хочу создать данные для моделирования. Основано на следующих логах c:

Ввод : 2 numpy массив с именами z и d.

z: массив 1d, значение 0/1

d: массив 1d, значение 0/1

Возврат: y: 1d массив. значение: норма случайных чисел.

Если z == 0 и d == 0, y ~ норма (1,1),

, если z == 0 и d == 1, y ~ норма (0,1),

, если z == 1 и d == 0, y ~ норма (1,1),

, если z == 1 и d == 1 , у ~ норма (2,1).

Я хочу сделать это очень быстро, ясно и pythoni c.

Кажется основ 1083 * математика и np.where быстрее. В этом случае у меня есть только 3 условия (вы можете ясно видеть из части математики basi c). Если у меня есть 10 или более условий, введите их в if-else утверждение иногда сбивает с толку. Я хочу выполнить моделирование данных, что означает, что я буду генерировать данные в миллионы раз с разными значениями n. Итак, как лучше это сделать?

Что я пробовал:

# generate data
n = 2000
z = np.random.binomial(1,0.5,n)
d = np.random.binomial(1,0.5,n)

случай, когда

def myfun(x):
    return {(0,1):np.random.normal(0,1),\
            (0,0):np.random.normal(1,1),\
            (1,0):np.random.normal(1,1),\
            (1,1):np.random.normal(2,1)}[x]
%%timeit
y = [myfun(i) for i in zip(z,d)]

Out:

16,2 мс ± 139 мкс на л oop (среднее ± стандартное отклонение из 7 прогонов, 100 циклов каждый)

простой if-else

%%timeit
y = np.random.normal([0 if (i == 0) & (j ==1) else 2 if (i == 1) & (j == 1) else 1 for i,j in zip(z,d)],1)

Out:

1,38 мс ± 22,1 мкс на л oop (среднее ± стандартное отклонение. Девять прогонов, 1000 циклов в каждом)

основы c математика

%%timeit
h0 = np.random.normal(0,1,n)
h1 = np.random.normal(1,1,n)
h2 = np.random.normal(2,1,n)
y = (1-z)*d*h0 + (1-d)*h1 + z*d*h2

Out:

140 мкс ± 135 нс на l oop (среднее ± стандартное отклонение из 7 прогонов, 10000 петель каждый)

np.where

%%timeit
h0 = np.random.normal(0,1,n)
h1 = np.random.normal(1,1,n)
h2 = np.random.normal(2,1,n)
y = np.where((d== 0),h1,0) + np.where((z ==1) & (d== 1),h2,0) + np.where((z ==0) & (d== 1),h0,0)

Out:

156 мкс ± 598 нс на л oop (среднее ± стандартное отклонение из 7 прогонов, по 10000 циклов в каждом)

Есть ли другой новый метод?

Ответы [ 2 ]

1 голос
/ 10 января 2020

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

import numpy as np

rng = np.random.default_rng()

# generate data
n = 2000
z = rng.binomial(1, 0.5, n)
d = rng.binomial(1, 0.5, n)

def generate_once(z, d):
    """Generate randoms for https://stackoverflow.com/questions/59676147"""

    # encode mean; scale is always 1 anyway in the example
    means = np.zeros_like(z, dtype=float)
    z_inds = z == 0
    d_inds = d == 0
    means[d_inds] = 1
    means[z_inds & ~d_inds] = 2

    # generate the data
    y = rng.normal(means)
    return y

y = generate_once(z, d)

Я не пытался сравнить это со всеми остальными, но я ожидал, что это будет конкурентоспособным. Считай это более быстрым вариантом твоего if-else. Использование ярлыков для отображения means (и в целом также scales) в качестве массива может снизить накладные расходы, а генерация каждого нормального числа ровно один раз должна сократить время выполнения.

1 голос
/ 10 января 2020

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

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

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

Кажется, вариант basic mathematics - лучший компромисс, поскольку он прост, легок для понимания и быстро выполняется.

Мой предвзятый обзор каждого метода:

  • dict case-when: медленный, потребует нескольких чтений / тестов, чтобы полностью понять, что на самом деле происходит, и выяснить, есть ли какие-либо неизвестные Готчас.
  • simple if-else: медленно, потребуется несколько чтений / тестов, чтобы полностью понять, что на самом деле происходит, и выяснить, есть ли какие-то неизвестные ошибки.
  • basic mathematics: быстро, Легко понять, если у вас есть даже небольшой математический фон (который должен включать большинство программистов).
  • np.where: быстро, выполняет работу, потребует нескольких чтений / тестов, чтобы полностью понять, что на самом деле происходит, но менее подвержен ошибкам, потому что основан на массивах.

Для справки приведем философию написания кода pythoni c:

  • Beautiful лучше, чем некрасиво.
  • Explicit лучше, чем неявный.
  • Простое лучше, чем сложное.
  • Комплексное лучше, чем сложное.
  • Плоское лучше, чем вложенное.
  • Разреженное лучше, чем плотное.
  • Читаемость имеет значение.

Используя вышеприведенные критерии, гораздо проще оценить, является ли ваш код pythoni c или нет.

...