Взвешенный процентиль с помощью numpy
Есть ли способ использовать функцию numpy.percentile
для вычисления взвешенного перцентиля? Или кто-нибудь знает альтернативную функцию на Python для вычисления взвешенного перцентиля?
5 ответ(ов)
Полностью векторизированное решение с использованием NumPy
Вот код, который я использую. Это не оптимальное решение (которое я не могу написать с помощью numpy
), но оно все равно значительно быстрее и надежнее, чем принятое решение.
def weighted_quantile(values, quantiles, sample_weight=None,
values_sorted=False, old_style=False):
""" Очень близко к numpy.percentile, но поддерживает веса.
ПРИМЕЧАНИЕ: квантильные значения должны быть в диапазоне [0, 1]!
:param values: numpy.array с данными
:param quantiles: массив с нужными квантилями
:param sample_weight: массив с весами той же длины, что и `values`
:param values_sorted: bool, если True, то избежать сортировки
начального массива
:param old_style: если True, выходные данные будут откорректированы для
согласованности с numpy.percentile.
:return: numpy.array с вычисленными квантилями.
"""
values = np.array(values)
quantiles = np.array(quantiles)
if sample_weight is None:
sample_weight = np.ones(len(values))
sample_weight = np.array(sample_weight)
assert np.all(quantiles >= 0) and np.all(quantiles <= 1), \
'квантильные значения должны быть в диапазоне [0, 1]'
if not values_sorted:
sorter = np.argsort(values)
values = values[sorter]
sample_weight = sample_weight[sorter]
weighted_quantiles = np.cumsum(sample_weight) - 0.5 * sample_weight
if old_style:
# Для удобства с numpy.percentile
weighted_quantiles -= weighted_quantiles[0]
weighted_quantiles /= weighted_quantiles[-1]
else:
weighted_quantiles /= np.sum(sample_weight)
return np.interp(quantiles, weighted_quantiles, values)
Примеры:
weighted_quantile([1, 2, 9, 3.2, 4], [0.0, 0.5, 1.])
Результат:
array([ 1. , 3.2, 9. ])
weighted_quantile([1, 2, 9, 3.2, 4], [0.0, 0.5, 1.], sample_weight=[2, 1, 2, 4, 1])
Результат:
array([ 1. , 3.2, 9. ])
Сейчас это, похоже, реализовано в библиотеке statsmodels. Вы можете использовать класс DescrStatsW
для вычисления обрабатываемых статистик с учетом весов. Вот пример:
from statsmodels.stats.weightstats import DescrStatsW
import numpy as np
wq = DescrStatsW(data=np.array([1, 2, 9, 3.2, 4]), weights=np.array([0.0, 0.5, 1.0, 0.3, 0.5]))
quantiles = wq.quantile(probs=np.array([0.1, 0.9]), return_pandas=False)
print(quantiles) # array([2., 9.])
Объект DescrStatsW
также включает в себя другие методы, такие как вычисление взвешенного среднего и многие другие. Вы можете ознакомиться с документацией здесь.
Быстрое решение, сначала сортируя, а затем интерполируя:
def weighted_percentile(data, percents, weights=None):
''' percents в единицах 1%
weights указывает частоту (количество) данных.
'''
if weights is None:
return np.percentile(data, percents)
ind = np.argsort(data)
d = data[ind]
w = weights[ind]
p = 1. * w.cumsum() / w.sum() * 100
y = np.interp(percents, p, d)
return y
В этом коде функция weighted_percentile
вычисляет взвешенный процентиль для заданного набора данных. Если веса не указаны, используется стандартный процентиль. Сначала данные сортируются, затем вычисляются кумулятивные суммы весов, после чего производится интерполяция для получения нужных процентилей.
Вопрос: Что такое взвешенный перцентиль?
Ответ: Не знаю, что именно означает взвешенный перцентиль, но из ответа @Joan Smith кажется, что нужно просто повторить каждый элемент в массиве ar
. Для этого можно использовать numpy.repeat()
:
import numpy as np
np.repeat([1, 2, 3], [4, 5, 6])
Результат будет следующим:
array([1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3])
Таким образом, каждый элемент из исходного массива повторяется соответственно указанному количеству раз.
Извините за дополнительные (неоригинальные) ответы (недостаточно репутации, чтобы прокомментировать @nayyarv). Его решение сработало для меня (т.е. оно дублирует поведение по умолчанию функции np.percentile
), но я думаю, что можно избавиться от цикла for
, учитывая, как написана оригинальная функция np.percentile
.
def weighted_percentile(a, q=np.array([75, 25]), w=None):
"""
Рассчитывает процентили, связанные с (возможно, взвешенным) массивом.
Параметры
----------
a : array-like
Входной массив, из которого следует вычислить проценты.
q : array-like
Процентили для вычисления (от 0.0 до 100.0).
w : array-like, опционально
Веса для значений в a. Равные веса, если указано None.
Возвращает
-------
values : np.array
Значения, связанные с указанными процентилями.
"""
# Стандартизировать и отсортировать по значениям в a
q = np.array(q) / 100.0
if w is None:
w = np.ones(a.size)
idx = np.argsort(a)
a_sort = a[idx]
w_sort = w[idx]
# Получить кумулятивную сумму весов
ecdf = np.cumsum(w_sort)
# Найти индексы процентилей
p = q * (w.sum() - 1)
# Найти границы индексов (нижний и верхний)
idx_low = np.searchsorted(ecdf, p, side='right')
idx_high = np.searchsorted(ecdf, p + 1, side='right')
idx_high[idx_high > ecdf.size - 1] = ecdf.size - 1
# Вычислить веса
weights_high = p - np.floor(p)
weights_low = 1.0 - weights_high
# Извлечь индексы низких / высоких значений и перемножить на соответствующие веса
x1 = np.take(a_sort, idx_low) * weights_low
x2 = np.take(a_sort, idx_high) * weights_high
# Вернуть среднее значение
return np.add(x1, x2)
# Пример данных
a = np.array([1.0, 2.0, 9.0, 3.2, 4.0], dtype=np.float)
w = np.array([2.0, 1.0, 3.0, 4.0, 1.0], dtype=np.float)
# Создание невзвешенной "копии" a для тестирования
a2 = np.repeat(a, w.astype(np.int))
# Тесты с разными выбранными процентилями
q1 = np.linspace(0.0, 100.0, 11)
q2 = np.linspace(5.0, 95.0, 10)
q3 = np.linspace(4.0, 94.0, 10)
for q in (q1, q2, q3):
assert np.all(weighted_percentile(a, q, w) == np.percentile(a2, q))
Таким образом, вы можете оценивать процентильные значения без использования циклов, что делает код более эффективным и читаемым.
Наиболее эффективный способ применения функции к массиву NumPy
Как задать верхние и нижние границы при использовании numpy.random.normal
Как извлечь частоту, связанную с FFT значениями в Python?
Цветовой график 2D массива в matplotlib
Преобразование байтового массива обратно в массив numpy