0

Взвешенный процентиль с помощью numpy

10

Есть ли способ использовать функцию numpy.percentile для вычисления взвешенного перцентиля? Или кто-нибудь знает альтернативную функцию на Python для вычисления взвешенного перцентиля?

5 ответ(ов)

0

Полностью векторизированное решение с использованием 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. ])
0

Сейчас это, похоже, реализовано в библиотеке 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 также включает в себя другие методы, такие как вычисление взвешенного среднего и многие другие. Вы можете ознакомиться с документацией здесь.

0

Быстрое решение, сначала сортируя, а затем интерполируя:

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 вычисляет взвешенный процентиль для заданного набора данных. Если веса не указаны, используется стандартный процентиль. Сначала данные сортируются, затем вычисляются кумулятивные суммы весов, после чего производится интерполяция для получения нужных процентилей.

0

Вопрос: Что такое взвешенный перцентиль?

Ответ: Не знаю, что именно означает взвешенный перцентиль, но из ответа @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])

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

0

Извините за дополнительные (неоригинальные) ответы (недостаточно репутации, чтобы прокомментировать @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))

Таким образом, вы можете оценивать процентильные значения без использования циклов, что делает код более эффективным и читаемым.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь