Как округлить число до указанной верхней или нижней связи? - PullRequest
0 голосов
/ 11 декабря 2018

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

например.если я хочу, чтобы верхняя граница была 9 , а нижняя - 3 , и у нас есть такие числа, как -

[ 7.453511737983394, 
  8.10917072790058, 
  6.2377799380575, 
  5.225853201122676, 
  4.067932296134156 ]

, и мы хотим, чтобы список был округлен до3 или 9, как -

[ 9, 
  9, 
  9, 
  3, 
  3 ]

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

my-approach-code:

for i in the_list[:]:
    three = abs(3-the_list[i])  
    nine = abs(9-the_list[i])

    if three < nine:
        the_list[i] = three
    else:
        the_list[i] = nine

Мне интересно, есть ли способ быстрый и грязный , который встроен в python, например:

hey_bound = round_the_num(number, bound_1, bound_2) 

Я знаючто мы можем my-approach-code, но я почти уверен, что там это было реализовано намного лучше, я пытался найти его, но безуспешно нашел его, и вот мы здесь.

любые догадкиили прямые ссылки для решения этой проблемы будут удивительными.

Ответы [ 8 ]

0 голосов
/ 12 декабря 2018

Сравнение времени доступных ответов

enter image description here

Моя интерпретация будетбыть:
С точки зрения производительности, вы должны использовать Abhishek Patel или Carles Mitjans для небольших списков.
Для списков, содержащих несколько десятков значений и более, массив значений и затем добавление условно различий с меньшими абсолютными значениями кажетсябыть самым быстрым решением.


Код, используемый для сравнения времени:

import timeit
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

plt.style.use('ggplot')

rep = 5

timings = dict()

for n in range(7):
    print(f'N = 10^{n}')

    N = 10**n
    setup = f'''import numpy as np\nthe_list = np.random.random({N})*6+3\nhi = 9\nlo = 3\ndlt = hi - lo\nmid = (hi + lo) / 2\ndef return_the_num(l, lst, h):\n    return [l if abs(l-x) < abs(h-x) else h for x in lst]'''

    fct = 'np.round((the_list - lo)/dlt) * dlt + lo'
    t = timeit.Timer(fct, setup=setup)
    timings['SpghttCd_np'] = timings.get('SpghttCd_np', []) + [np.min(t.repeat(repeat=rep, number=1))]

    fct = 'return_the_num(3, the_list, 9)'
    t = timeit.Timer(fct, setup=setup)
    timings['Austin'] = timings.get('Austin', []) + [np.min(t.repeat(repeat=rep, number=1))]

    fct = '[(lo, hi)[mid < v] for v in the_list]'
    t = timeit.Timer(fct, setup=setup)
    timings['SpghttCd_lc'] = timings.get('SpghttCd_lc', []) + [np.min(t.repeat(repeat=rep, number=1))]

    setup += '\nround_the_num = lambda list, upper, lower: [upper if x > (upper + lower) / 2 else lower for x in list]'
    fct = 'round_the_num(the_list, 9, 3)'
    t = timeit.Timer(fct, setup=setup)
    timings['Carles Mitjans'] = timings.get('Carles Mitjans', []) + [np.min(t.repeat(repeat=rep, number=1))]

    setup += '\nupper_lower_bound_list=[3,9]'
    fct = '[min(upper_lower_bound_list, key=lambda x:abs(x-myNumber)) for myNumber in the_list]'
    t = timeit.Timer(fct, setup=setup)
    timings['mad_'] = timings.get('mad_', []) + [np.min(t.repeat(repeat=rep, number=1))]

    setup += '\ndef return_bound(x, l, h):\n    low = abs(x - l)\n    high = abs(x - h)\n    if low < high:\n        return l\n    else:\n        return h'
    fct = '[return_bound(x, 3, 9) for x in the_list]'
    t = timeit.Timer(fct, setup=setup)
    timings["Scratch'N'Purr"] = timings.get("Scratch'N'Purr", []) + [np.min(t.repeat(repeat=rep, number=1))]

    setup += '\ndef round_the_list(list, bound_1, bound_2):\n\tmid = (bound_1+bound_2)/2\n\tfor i in range(len(list)):\n\t\tif list[i] > mid:\n\t\t\tlist[i] = bound_2\n\t\telse:\n\t\t\tlist[i] = bound_1'
    fct = 'round_the_list(the_list, 3, 9)'
    t = timeit.Timer(fct, setup=setup)
    timings["Abhishek Patel"] = timings.get("Abhishek Patel", []) + [np.min(t.repeat(repeat=rep, number=1))]

    fct = 'dhi = 9 - the_list\ndlo = 3 - the_list\nidx = dhi + dlo < 0\nthe_list + np.where(idx, dhi, dlo)'
    t = timeit.Timer(fct, setup=setup)
    timings["SpghttCd_where"] = timings.get("SpghttCd_where", []) + [np.min(t.repeat(repeat=rep, number=1))]

print('done')

df = pd.DataFrame(timings, 10**np.arange(n+1))
ax = df.plot(logx=True, logy=True)
ax.set_xlabel('length of the list')
ax.set_ylabel('seconds to run')
ax.get_lines()[-1].set_c('g')
plt.legend()
print(df)
0 голосов
/ 11 декабря 2018

Вы можете написать пользовательскую функцию, которая выполняет понимание списка, например:

lst = [ 7.453511737983394, 
  8.10917072790058, 
  6.2377799380575, 
  5.225853201122676, 
  4.067932296134156 ]

def return_the_num(l, lst, h): 
    return [l if abs(l-x) < abs(h-x) else h for x in lst]

print(return_the_num(3, lst, 9))
# [9, 9, 9, 3, 3]
0 голосов
/ 11 декабря 2018

Мне очень нравится идея @ AbhishekPatel о сравнении со средней точкой.Но я бы поместил его в LC, используя результат в качестве индекса для кортежа границ:

the_list = [ 7.453511737983394,
8.10917072790058, 
6.2377799380575, 
5.225853201122676, 
4.067932296134156 ]

hi = 9
lo = 3
mid = (hi + lo) / 2

[(lo, hi)[mid < v] for v in the_list]
# [9, 9, 9, 3, 3]

... но это более чем в 15 раз медленнее, чем простой подход.
Однако здесь это может обрабатывать числа больше hi или ниже lo.
... но это опять-таки только для списка записей 100000.В случае с оригинальным списком, опубликованным OP, два варианта очень близки друг к другу ...

0 голосов
/ 11 декабря 2018

Еще одна опция, использующая списки и лямбда-функции:

round_the_num = lambda list, upper, lower: [upper if x > (upper + lower) / 2 else lower for x in list]

round_the_num(l, 9, 3)
0 голосов
/ 11 декабря 2018

Понимание списка из одной строки с использованием встроенной функции min путем изменения ключевого аргумента для поиска абсолютной разницы

upper_lower_bound_list=[3,9]
myNumberlist=[ 7.453511737983394, 
8.10917072790058, 
6.2377799380575, 
5.225853201122676, 
4.067932296134156 ]

Понимание списка

[min(upper_lower_bound_list, key=lambda x:abs(x-myNumber)) for myNumber in myNumberlist]

Вывод

[9, 9, 9, 3, 3]
0 голосов
/ 11 декабря 2018

РЕДАКТИРОВАТЬ:
На мой взгляд, лучший подход до сих пор заключается в использовании numpy (чтобы избежать "ручного" зацикливания) с простым расчетом массивов разностей между the_list и двумя границами(здесь нет дорогостоящего умножения), чтобы затем только условно добавить одно или другое, в зависимости от того, что меньше:

import numpy as np

the_list = np.array([ 7.453511737983394,
8.10917072790058, 
6.2377799380575, 
5.225853201122676, 
4.067932296134156 ])

dhi = 9 - the_list
dlo = 3 - the_list
idx = dhi + dlo < 0
the_rounded = the_list + np.where(idx, dhi, dlo)
# array([9., 9., 9., 3., 3.])

Я бы применил функцию округления к нормализованному списку и шкале без смещенийназад и добавьте смещение потом:

import numpy as np

the_list = np.array([ 7.453511737983394,
8.10917072790058, 
6.2377799380575, 
5.225853201122676, 
4.067932296134156 ])

hi = 9
lo = 3
dlt = hi - lo

the_rounded = np.round((the_list - lo)/dlt) * dlt + lo

# [9. 9. 9. 3. 3.]
0 голосов
/ 11 декабря 2018

Возможно, вы могли бы написать функцию и использовать ее для понимания списка.

def return_bound(x, l, h):
    low = abs(x - l)
    high = abs(x - h)
    if low < high:
        return l
    else:
        return h

Тест:

>>> mylist = [7.453511737983394, 8.10917072790058, 6.2377799380575, 5.225853201122676, 4.067932296134156]
>>> [return_bound(x, 3, 9) for x in mylist]
[9, 9, 9, 3, 3]
0 голосов
/ 11 декабря 2018

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

def round_the_list(list, bound_1, bound_2):
  mid = (bound_1+bound_2)/2
  for i in range(len(list)):
        if list[i] > mid:         # or >= depending on your rounding decision
            list[i] = bound_2
        else:
            list[i] = bound_1
...