Заменить элементы в списке другими элементами (небольшими списками) в зависимости от нескольких условий - PullRequest
2 голосов
/ 12 марта 2019

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

Спасибо

import numpy as np
n = []
z = np.linspace(0,5,8)
t = [3.8856, 4.1820, 2.3040, 1.0197, 0.4295, 1.5178, 0.3853, 4.2848, 4.30911, 3.2299, 1.8528, 0.6553, 3.3305, 4.1504, 1.8787]
for i in t:
    if i>=z[0] and i<z[1]:
        n.extend([0,0,0,0,0])
    elif i>=z[1] and i<z[2]:
        n.extend([0,0,0,0,1])
    elif i>=z[2] and i<z[3]:
        n.extend([0,0,0,1,0])
    elif i>=z[3] and i<z[4]:
        n.extend([0,0,0,1,1])
    elif i>=z[4] and i<z[5]:
        n.extend([0,0,1,0,0])
    elif i>=z[5] and i<z[6]:
        n.extend([0,0,1,0,1])
    elif i>=z[6] and i<z[7]:
        n.extend([0,0,1,1,0])
new_n = np.asarray(n).reshape(len(t),5) # new_n is the final pattern I want.

Ответы [ 3 ]

2 голосов
/ 12 марта 2019

Это не ответ сам по себе, но, вероятно, он будет быстрее из-за использования numpy, а не цикла python for.

Сначала вы хотите выполнить binning :

>> bins = np.digitize(t, z) - 1 # minus 1 just to align our shapes
array([5, 5, 3, 1, 0, 2, 0, 5, 6, 4, 2, 0, 4, 5, 2])

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

>> patterns = np.array([
    [0,0,0,0,0],
    [0,0,0,0,1],
    [0,0,0,1,0],
    [0,0,0,1,1],
    [0,0,1,0,0],
    [0,0,1,0,1],
    [0,0,1,1,0],
])

Теперь для некоторой непонятной магии вместо добавления / расширения создайте массив, полный нулей (это должно быть почти всегда быстрее). Этот массив будет иметь форму (len(t), len(z)-1). Используя этот SO-ответ , мы также сделаем горячее кодирование:

>> inds = np.zeros((len(t), len(z)-1))
>> inds[np.arange(len(t)), bins] = 1
>> inds
array([[0., 0., 0., 0., 0., 1., 0.],
       [0., 0., 0., 0., 0., 1., 0.],
       [0., 0., 0., 1., 0., 0., 0.],
       .....,
       [0., 0., 0., 0., 0., 1., 0.],
       [0., 0., 1., 0., 0., 0., 0.]])

Наконец, все, что нам нужно, это умножение матриц

>> inds @ patterns
array([[0., 0., 1., 0., 1.],
       [0., 0., 1., 0., 1.],
       [0., 0., 0., 1., 1.],
       ....
       [0., 0., 1., 0., 1.],
       [0., 0., 0., 1., 0.]])

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

Ваш цикл: 17,7 мкс ± 160 нс на цикл (среднее ± стандартное отклонение из 7 циклов, 100000 циклов в каждом) Моя реализация: 8,49 мкс ± 125 нс на цикл (среднее ± стандартное отклонение из 7 циклов, 100000 циклов каждый)

Что может или не может хорошо масштабироваться для больших наборов данных. Надеюсь, это поможет:)


Редактировать: После ответа Александра Лопатина Мне было интересно увидеть, что мой метод был значительно медленнее. После дальнейшего изучения один из выводов, к которым я пришел, заключался в том, что функции numpy имеют некоторые существенные накладные расходы, что является недешевой ценой, которую приходится платить за несколько значений t. Для больших списков незначительные накладные расходы незначительны, но прирост производительности не равен:

enter image description here

timings = {
    10: [7.79, 24.1, 21.7],
    16: [10.7, 29.9, 22.9],
    24: [14.6, 40.5, 23.4],
    33: [19.1, 48.6, 23.4],
    38: [21.9, 55.9, 23.9],
    47: [26.7, 66.2, 24.1],
    61: [33, 79.5, 24.7],
    75: [40.8, 92.6, 25.8],
    89: [47.6, 108, 26.2],
    118: [60.1, 136, 27.4],
    236: [118, 264, 33.1],
    472: [236, 495, 40.9],
    1000: [657, 922, 52],
    10000: [6530, 9090, 329]
}

Zoom:

enter image description here

1 голос
/ 12 марта 2019

Моя новая версия в три раза быстрее оригинала:

Time    CPU for 100000 loops
1.7444  1.7400 proposed by Alexander Lopatin
5.2813  5.2770 original by motaha
4.6203  4.6117 proposed by Kostas Mouratidis

Я упростил elif s, чтобы уменьшить исходный код (11 строк), а затем добавил 57 строк (66..123) для проверки скорости и корректности :-) Попытался также использовать z = np.linspace (0,5,8) или предварительно рассчитать z вне цикла for in, если z [j] j (j + 1): ', но получил большой штраф времени - не знаю почему. Я также добавил код, предложенный здесь Костасом Муратидисом. Он не дал точного результата, смотрите вывод в конце.

import numpy as np
import itertools
import time
import platform


def f1():  # answered by Alexander Lopatin #####################################
    n = []
    t = [3.8856, 4.1820, 2.3040, 1.0197,  0.4295,
         1.5178, 0.3853, 4.2848, 4.30911, 3.2299,
         1.8528, 0.6553, 3.3305, 4.1504,  1.8787]
    x = 5./7.
    p = list(itertools.product([0, 1], repeat=5))
    for y in t:
        j = int(y/x)
        if x*j < y < x*(j+1):
            n.append(p[j])
    return np.asarray(n).reshape(len(t), 5)


def f2():  # original post by motaha ###########################################
    n = []
    t = [3.8856, 4.1820, 2.3040, 1.0197, 0.4295,
         1.5178, 0.3853, 4.2848, 4.30911,3.2299,
         1.8528, 0.6553, 3.3305, 4.1504, 1.8787]
    z = np.linspace(0,5,8)
    for i in t:
        if i>=z[0] and i<z[1]:
            n.extend([0,0,0,0,0])
        elif i>=z[1] and i<z[2]:
            n.extend([0,0,0,0,1])
        elif i>=z[2] and i<z[3]:
            n.extend([0,0,0,1,0])
        elif i>=z[3] and i<z[4]:
            n.extend([0,0,0,1,1])
        elif i>=z[4] and i<z[5]:
            n.extend([0,0,1,0,0])
        elif i>=z[5] and i<z[6]:
            n.extend([0,0,1,0,1])
        elif i>=z[6] and i<z[7]:
            n.extend([0,0,1,1,0])
    return np.asarray(n).reshape(len(t),5)


def f3(): # answered by Kostas Mouratidis ######################################
    n = []
    t = [3.8856, 4.1820, 2.3040, 1.0197, 0.4295,
         1.5178, 0.3853, 4.2848, 4.30911,3.2299,
         1.8528, 0.6553, 3.3305, 4.1504, 1.8787]
    z = np.linspace(0,5,8)
    bins = np.digitize(t, z) - 1  # minus 1 just to align our shapes
    patterns = np.array([
        [0, 0, 0, 0, 1],
        [0, 0, 0, 0, 1],
        [0, 0, 0, 1, 0],
        [0, 0, 0, 1, 1],
        [0, 0, 1, 0, 0],
        [0, 0, 1, 0, 1],
        [0, 0, 1, 1, 1],
    ])
    inds = np.zeros((len(t), len(z) - 1), dtype=int)
    inds[np.arange(len(t)), bins] = 1
    inds = inds @ patterns
    return inds

# Testing ... ##################################################################


def correct_cpu(cpu_time):
    pv1, pv2, _ = platform.python_version_tuple()
    pcv = platform.python_compiler()
    if pv1 == '3' and '5' <= pv2 <= '8' and pcv == 'Clang 6.0 (clang-600.0.57)':
        cpu_time /= 2.0
    return cpu_time


def test(test_function, test_loops, test_name):
    t = time.perf_counter()
    c = time.process_time()
    test_result = []
    for j in range(0, test_loops):
        test_result = test_function()
    t = time.perf_counter() - t
    c = correct_cpu(time.process_time() - c)
    print('%.4f  %.4f %s' % (t, c, test_name))
    return test_result

print('Python version  :', platform.python_version())
print('       build    :', platform.python_build())
print('       compiler :', platform.python_compiler())
print()
loops = 100000
f2test = [(f1, 'proposed by Alexander Lopatin'),
          (f2, 'original by motaha'),
          (f3, 'proposed by Kostas Mouratidis')]
print('Time    CPU for', loops, 'loops')

results = []
for func, name in f2test:
    results.append(test(func, loops, name))

original = 1
_, name = f2test[original]
print('\nthe final pattern I want! ' + name)
print(results[original])
for order, result in enumerate(results):
    if order == original:
        continue
    _, name = f2test[order]
    error = False
    for i_row, row in enumerate(result):
        for j_column, value in enumerate(row):
            if value != results[original][i_row][j_column]:
                error = True
                print('\n*** Check for ERRORS in (%d,%d) %s '
                      % (i_row, j_column, name))
                break
        if error:
            break
    if error:
        print(result)
    else:
        print('The same ' + name)

Выход:

Python version  : 3.8.0a2
       build    : ('v3.8.0a2:23f4589b4b', 'Feb 25 2019 10:59:08')
       compiler : Clang 6.0 (clang-600.0.57)

Time    CPU for 100000 loops
1.7444  1.7400 proposed by Alexander Lopatin
5.2813  5.2770 original by motaha
4.6203  4.6117 proposed by Kostas Mouratidis

the final pattern I want! original by motaha
[[0 0 1 0 1]
 [0 0 1 0 1]
 [0 0 0 1 1]
 [0 0 0 0 1]
 [0 0 0 0 0]
 [0 0 0 1 0]
 [0 0 0 0 0]
 [0 0 1 0 1]
 [0 0 1 1 0]
 [0 0 1 0 0]
 [0 0 0 1 0]
 [0 0 0 0 0]
 [0 0 1 0 0]
 [0 0 1 0 1]
 [0 0 0 1 0]]
The same proposed by by Alexander Lopatin

*** Check for ERRORS in (4,4) proposed by Kostas Mouratidis 
[[0 0 1 0 1]
 [0 0 1 0 1]
 [0 0 0 1 1]
 [0 0 0 0 1]
 [0 0 0 0 1]
 [0 0 0 1 0]
 [0 0 0 0 1]
 [0 0 1 0 1]
 [0 0 1 1 1]
 [0 0 1 0 0]
 [0 0 0 1 0]
 [0 0 0 0 1]
 [0 0 1 0 0]
 [0 0 1 0 1]
 [0 0 0 1 0]]
0 голосов
/ 12 марта 2019

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

В противном случае единственное реальное улучшение, которое можно сделать, - это сжатые сравнения, такие как z[0]<=i<z[1].

...